@crewx/workflow 0.3.22-rc.220 → 0.3.22-rc.221

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 CHANGED
@@ -149,6 +149,17 @@ Auto mode pauses instead of crossing these boundaries:
149
149
 
150
150
  `max_iterations` limits auto-run steps and prevents infinite loops.
151
151
 
152
+ ### `workflow run approve <exec-id>` / `workflow run reject <exec-id>`
153
+
154
+ Records the human decision for the approval node the run is paused at, appends it to the run's `audit[]`, and moves the run to the node's `on_approve` / `on_reject` target. Continue with `workflow run auto`.
155
+
156
+ ```bash
157
+ crewx workflow run approve wfr_abc12345
158
+ crewx workflow run reject wfr_abc12345 --reason="rollback plan missing"
159
+ ```
160
+
161
+ Agent processes cannot approve or reject: the command refuses when `CREWX_AGENT_ID` is set. Only a person runs it from a shell.
162
+
152
163
  ### `workflow run reset <exec-id>`
153
164
 
154
165
  Restores execution state to `initial_state`.
package/dist/cli.js CHANGED
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
- "use strict";var z=exports&&exports.__createBinding||(Object.create?(function(e,o,t,n){if(n===void 0)n=t;var s=Object.getOwnPropertyDescriptor(o,t);if(!s||("get"in s?!o.__esModule:s.writable||s.configurable)){s={enumerable:true,get:function(){return o[t]}}}Object.defineProperty(e,n,s)}):(function(e,o,t,n){if(n===void 0)n=t;e[n]=o[t]}));var Q=exports&&exports.__setModuleDefault||(Object.create?(function(e,o){Object.defineProperty(e,"default",{enumerable:true,value:o})}):function(e,o){e["default"]=o});var J=exports&&exports.__importStar||(function(){var e=function(o){e=Object.getOwnPropertyNames||function(t){var n=[];for(var s in t)if(Object.prototype.hasOwnProperty.call(t,s))n[n.length]=s;return n};return e(o)};return function(o){if(o&&o.__esModule)return o;var t={};if(o!=null){for(var n=e(o),s=0;s<n.length;s++)if(n[s]!=="default")z(t,o,n[s])}Q(t,o);return t}})();Object.defineProperty(exports,"__esModule",{value:true});exports.resolveWorkflow=A;exports.findNextNodeForCAL=X;exports.getFlowChain=H;exports.main=L;const S=J(require("fs"));const D=require("./src/list-view");const y=J(require("path"));const Z=require("child_process");const W=require("@crewx/sdk");const B=require("@crewx/sdk/repository");const V=require("./src/engine");const I=require("./src/run-manager");const R=require("./src/workflow-persistence");const K=require("./src/parent-task-sync");const $=require("@crewx/knowledge-core");let E="unknown";try{const e=S.existsSync(y.join(__dirname,"package.json"))?y.join(__dirname,"package.json"):y.join(__dirname,"..","package.json");const o=JSON.parse(S.readFileSync(e,"utf-8"));if(o?.version)E=String(o.version)}catch{}async function M(){try{return await Promise.resolve().then(()=>J(require("@crewx/shared/skill-tracer")))}catch{return null}}const ee=new Set(["help","json","mermaid","version","no-exec","auto","no-run","apply","dry-run"]);function oe(e){const o={positional:[],options:{}};for(let t=0;t<e.length;t++){const n=e[t];if(n==="-h"){o.options["help"]=true}else if(n.startsWith("--")){const s=n.indexOf("=");if(s!==-1){o.options[n.slice(2,s)]=n.slice(s+1)}else{const l=n.slice(2);if(ee.has(l)){o.options[l]=true}else{const a=e[t+1];if(a!==void 0&&!a.startsWith("--")&&a!=="-h"){o.options[l]=a;t++}else{o.options[l]=true}}}}else{o.positional.push(n)}}return o}function F(){try{return(0,Z.execSync)("git rev-parse --show-toplevel",{encoding:"utf8"}).trim()}catch{return process.cwd()}}function O(){return(0,D.readWorkflowConfig)(process.env.CREWX_CONFIG??y.join(F(),"crewx.yaml"))}function te(e,o,t,n){if(n)return null;const s=[...e.audit??[]].reverse().find(l=>l.node_id===o&&l.type!=="retry");if(!s)return null;if(s.error||t==="error")return"failed";if(t==="approval"||t==="parallel")return"paused";return"completed"}function se(e){if(!e.endsWith(".yaml")&&!e.endsWith(".yml")){if(/[/\\]|\.\./.test(e)){throw new Error(`Invalid workflow name: "${e}"`)}}}function A(e,o,t,n){if(e.endsWith(".yaml")||e.endsWith(".yml")){const i=y.resolve(process.cwd(),e);if(!S.existsSync(i)){throw new Error(`Workflow file not found: ${i}`)}return{filePath:i,resolvedVia:"path"}}se(e);const s=n??N(t,o);const l=s.find(i=>i.id===e);if(l){const i=s.filter(k=>k.id===e&&k.filePath!==l.filePath);if(i.length>0){throw new Error(`Duplicate workflow ID "${e}" found in multiple files:
2
+ "use strict";var M=exports&&exports.__createBinding||(Object.create?(function(e,o,t,n){if(n===void 0)n=t;var s=Object.getOwnPropertyDescriptor(o,t);if(!s||("get"in s?!o.__esModule:s.writable||s.configurable)){s={enumerable:true,get:function(){return o[t]}}}Object.defineProperty(e,n,s)}):(function(e,o,t,n){if(n===void 0)n=t;e[n]=o[t]}));var ee=exports&&exports.__setModuleDefault||(Object.create?(function(e,o){Object.defineProperty(e,"default",{enumerable:true,value:o})}):function(e,o){e["default"]=o});var E=exports&&exports.__importStar||(function(){var e=function(o){e=Object.getOwnPropertyNames||function(t){var n=[];for(var s in t)if(Object.prototype.hasOwnProperty.call(t,s))n[n.length]=s;return n};return e(o)};return function(o){if(o&&o.__esModule)return o;var t={};if(o!=null){for(var n=e(o),s=0;s<n.length;s++)if(n[s]!=="default")M(t,o,n[s])}ee(t,o);return t}})();Object.defineProperty(exports,"__esModule",{value:true});exports.resolveWorkflow=P;exports.findNextNodeForCAL=z;exports.getFlowChain=Q;exports.main=V;const v=E(require("fs"));const D=require("./src/list-view");const $=E(require("path"));const oe=require("child_process");const W=require("@crewx/sdk");const T=require("@crewx/sdk/repository");const G=require("./src/engine");const I=require("./src/run-manager");const j=require("./src/workflow-persistence");const K=require("./src/parent-task-sync");const te=require("./src/run-anchor");const x=require("@crewx/knowledge-core");let U="unknown";try{const e=v.existsSync($.join(__dirname,"package.json"))?$.join(__dirname,"package.json"):$.join(__dirname,"..","package.json");const o=JSON.parse(v.readFileSync(e,"utf-8"));if(o?.version)U=String(o.version)}catch{}async function se(){try{return await Promise.resolve().then(()=>E(require("@crewx/shared/skill-tracer")))}catch{return null}}const ne=new Set(["help","json","mermaid","version","no-exec","auto","no-run","apply","dry-run"]);function X(e){const o={positional:[],options:{}};for(let t=0;t<e.length;t++){const n=e[t];if(n==="-h"){o.options["help"]=true}else if(n.startsWith("--")){const s=n.indexOf("=");if(s!==-1){o.options[n.slice(2,s)]=n.slice(s+1)}else{const l=n.slice(2);if(ne.has(l)){o.options[l]=true}else{const u=e[t+1];if(u!==void 0&&!u.startsWith("--")&&u!=="-h"){o.options[l]=u;t++}else{o.options[l]=true}}}}else{o.positional.push(n)}}return o}function F(){try{return(0,oe.execSync)("git rev-parse --show-toplevel",{encoding:"utf8"}).trim()}catch{return process.cwd()}}function b(){return(0,D.readWorkflowConfig)(process.env.CREWX_CONFIG??$.join(F(),"crewx.yaml"))}function re(e,o,t,n){if(n)return null;const s=[...e.audit??[]].reverse().find(l=>l.node_id===o&&l.type!=="retry");if(!s)return null;if(s.error||t==="error")return"failed";if(t==="approval"||t==="parallel")return"paused";return"completed"}function ie(e){if(!e.endsWith(".yaml")&&!e.endsWith(".yml")){if(/[/\\]|\.\./.test(e)){throw new Error(`Invalid workflow name: "${e}"`)}}}function P(e,o,t,n){if(e.endsWith(".yaml")||e.endsWith(".yml")){const i=$.resolve(process.cwd(),e);if(!v.existsSync(i)){throw new Error(`Workflow file not found: ${i}`)}return{filePath:i,resolvedVia:"path"}}ie(e);const s=n??O(t,o);const l=s.find(i=>i.id===e);if(l){const i=s.filter(y=>y.id===e&&y.filePath!==l.filePath);if(i.length>0){throw new Error(`Duplicate workflow ID "${e}" found in multiple files:
3
3
  - ${l.filePath}
4
- `+i.map(k=>` - ${k.filePath}`).join("\n")+`
5
- Use file path to disambiguate: workflow run start <path.yaml>`)}return{filePath:l.filePath,workflowId:l.id,resolvedVia:"id"}}for(const i of o){const k=y.resolve(process.cwd(),i);for(const g of[".yaml",".yml"]){const d=y.join(k,`${e}${g}`);if(S.existsSync(d)){return{filePath:d,resolvedVia:"slug"}}}}const a=s.map(i=>i.id);throw new Error(`Workflow "${e}" not found (searched as ID and file slug).
4
+ `+i.map(y=>` - ${y.filePath}`).join("\n")+`
5
+ Use file path to disambiguate: workflow run start <path.yaml>`)}return{filePath:l.filePath,workflowId:l.id,resolvedVia:"id"}}for(const i of o){const y=$.resolve(process.cwd(),i);for(const k of[".yaml",".yml"]){const m=$.join(y,`${e}${k}`);if(v.existsSync(m)){return{filePath:m,resolvedVia:"slug"}}}}const u=s.map(i=>i.id);throw new Error(`Workflow "${e}" not found (searched as ID and file slug).
6
6
  Searched directories: ${o.join(", ")}
7
- `+(a.length>0?` Available workflows: ${a.join(", ")}
8
- `:"")+` Hint: use "workflow list" to see all workflow IDs`)}function N(e,o){return(0,D.loadWorkflowPaths)(o,process.cwd())}function G(e,o){const t=y.resolve(e.workflow_file);const n=o.loadWorkflows(y.dirname(t));return n?.find(s=>y.resolve(s.filePath)===t&&s.id===e.workflow_id)?.spec}function U(){console.log(`
9
- @crewx/workflow@${E} - Workflow management for CrewX
7
+ `+(u.length>0?` Available workflows: ${u.join(", ")}
8
+ `:"")+` Hint: use "workflow list" to see all workflow IDs`)}function O(e,o){return(0,D.loadWorkflowPaths)(o,process.cwd())}function q(e,o){const t=$.resolve(e.workflow_file);const n=o.loadWorkflows($.dirname(t));return n?.find(s=>$.resolve(s.filePath)===t&&s.id===e.workflow_id)?.spec}function L(){console.log(`
9
+ @crewx/workflow@${U} - Workflow management for CrewX
10
10
 
11
11
  Usage:
12
12
  workflow <command> [args] [options]
@@ -23,6 +23,8 @@ Commands:
23
23
  workflow run reset <id> Reset execution
24
24
  workflow run retry <id> Retry a failed run from the failed node (--no-run: transition only)
25
25
  \u26A0\uFE0F re-executes the failed node; safe only if it is idempotent
26
+ workflow run approve <id> Approve the approval step the run is waiting at
27
+ workflow run reject <id> Reject the approval step (--reason=<text> is kept as plan feedback)
26
28
  workflow run list List all executions
27
29
  workflow repair <run-id> Preview parent-task repair (use --apply to write)
28
30
  workflow usage Show detailed guide
@@ -45,10 +47,10 @@ Examples:
45
47
  workflow validate workflows/standard-dev.yaml
46
48
 
47
49
  For detailed usage guide: workflow usage
48
- `)}function ne(){const e=y.join(__dirname,"..","SKILL.md");if(S.existsSync(e)){process.stdout.write(S.readFileSync(e,"utf-8"))}else{U()}}function re(e){const o=[];let t=1;for(const[n,s]of Object.entries(e)){if(!s||typeof s!=="object")continue;const l=s.type??"unknown";let a=`${n} (${l}`;if(s.agent)a+=` \u2192 ${s.agent}`;a+=")";if(l==="parallel"&&Array.isArray(s.branches)){a+=` \u2192 [${s.branches.join(", ")}]`;if(s.join)a+=` \u2192 ${s.join}`}else if(l==="branch"&&s.branches&&typeof s.branches==="object"&&!Array.isArray(s.branches)){const i=Object.entries(s.branches).map(([k,g])=>`${k}: ${g}`).join(" / ");a+=` \u2192 ${i}`}else if(s.next){a+=` \u2192 ${s.next}`}o.push(` ${t}. ${a}`);t++}return o}function ie(e,o){const{paths:t}=O();console.log((0,D.formatWorkflowList)({paths:t,workflows:N(e,t),jsonMode:o}))}function le(e,o,t,n){const{paths:s}=O();const l=N(e,s);let a;try{const m=A(o,s,e,l);if(m.workflowId){a=l.find(c=>c.id===m.workflowId)}else{const c=l.filter(f=>f.filePath===m.filePath);if(c.length===1){a=c[0]}else if(c.length>1){throw new Error(`File "${y.basename(m.filePath)}" contains ${c.length} workflows.
50
+ `)}function le(){const e=$.join(__dirname,"..","SKILL.md");if(v.existsSync(e)){process.stdout.write(v.readFileSync(e,"utf-8"))}else{L()}}function ce(e){const o=[];let t=1;for(const[n,s]of Object.entries(e)){if(!s||typeof s!=="object")continue;const l=s.type??"unknown";let u=`${n} (${l}`;if(s.agent)u+=` \u2192 ${s.agent}`;u+=")";if(l==="parallel"&&Array.isArray(s.branches)){u+=` \u2192 [${s.branches.join(", ")}]`;if(s.join)u+=` \u2192 ${s.join}`}else if(l==="branch"&&s.branches&&typeof s.branches==="object"&&!Array.isArray(s.branches)){const i=Object.entries(s.branches).map(([y,k])=>`${y}: ${k}`).join(" / ");u+=` \u2192 ${i}`}else if(s.next){u+=` \u2192 ${s.next}`}o.push(` ${t}. ${u}`);t++}return o}function ae(e,o){const{paths:t}=b();console.log((0,D.formatWorkflowList)({paths:t,workflows:O(e,t),jsonMode:o}))}function ue(e,o,t,n){const{paths:s}=b();const l=O(e,s);let u;try{const w=P(o,s,e,l);if(w.workflowId){u=l.find(g=>g.id===w.workflowId)}else{const g=l.filter(p=>p.filePath===w.filePath);if(g.length===1){u=g[0]}else if(g.length>1){throw new Error(`File "${$.basename(w.filePath)}" contains ${g.length} workflows.
49
51
  Specify a workflow ID:
50
- `+c.map(f=>` workflow show ${f.id}`).join("\n"))}}}catch(m){const c=m.message;if(t){console.log(JSON.stringify({success:false,error:c}))}else{console.error(`\u274C ${c}`)}process.exit(1)}if(!a){const m=`Workflow "${o}" not found in: ${s.join(", ")}`;if(t){console.log(JSON.stringify({success:false,error:m}))}else{console.error(`\u274C ${m}`)}process.exit(1)}if(n){console.log(e.toMermaid(a.spec));return}const{spec:i}=a;const k=i.metadata??{};const g=i.nodes??{};const d=i.state?Object.keys(i.state):[];if(t){console.log(JSON.stringify({success:true,workflow:{id:a.id,name:k.name??a.id,description:k.description??"",timeout:i.timeout??null,max_iterations:i.max_iterations??null,state:d,node_count:Object.keys(g).length,nodes:Object.entries(g).map(([m,c])=>({id:m,type:c.type,agent:c.agent??null,next:c.next??null,branches:c.branches??null,join:c.join??null}))}},null,2))}else{const m=k.description?` \u2014 ${k.description}`:"";console.log(`\u{1F4C4} ${a.id}${m}`);console.log(` \u{1F4C1} ${a.filePath}`);const c=[];if(i.timeout)c.push(`timeout: ${i.timeout}s`);if(i.max_iterations)c.push(`max_iterations: ${i.max_iterations}`);if(c.length>0)console.log(` ${c.join(" | ")}`);if(d.length>0){console.log(`
51
- State: ${d.join(", ")}`)}const f=re(g);if(f.length>0){console.log(`
52
- Flow:`);f.forEach(r=>console.log(r))}const u={commandId:"workflow.show",params:{name:o},resultCount:Object.keys(g).length};const w=(0,$.generateNextActions)(u);const p=(0,$.formatActions)(w);if(p)console.log(p)}}function ce(e,o,t){const{paths:n}=O();let s;let l;if(o){try{const g=A(o,n,e);if(!S.existsSync(g.filePath)){const d=`File not found: ${g.filePath}`;if(t){console.log(JSON.stringify({success:false,error:d,workflows:[]}))}else{console.error(`\u274C ${d}`)}process.exit(2)}s=[g.filePath];if(!o.endsWith(".yaml")&&!o.endsWith(".yml")){l=o}}catch(g){const d=g.message;if(t){console.log(JSON.stringify({success:false,error:d,workflows:[]}))}else{console.error(`\u274C ${d}`)}process.exit(2)}}else{s=[];for(const g of n){const d=y.resolve(process.cwd(),g);if(!S.existsSync(d)||!S.statSync(d).isDirectory())continue;const m=S.readdirSync(d).filter(c=>c.endsWith(".yaml")||c.endsWith(".yml")).map(c=>y.join(d,c));s.push(...m)}}if(s.length===0){const g="No workflow YAML files found";if(t){console.log(JSON.stringify({success:true,message:g,workflows:[]}))}else{console.log(`\u2139\uFE0F ${g}`)}return}let a=false;const i=[];let k;for(const g of s){const{success:d,results:m,error:c}=e.validateFile(g);if(c){a=true;if(t){i.push({file:g,results:[{name:y.basename(g),valid:false,errors:[{path:g,message:c}]}]})}else{console.error(`\u274C ${g}: ${c}
53
- `)}continue}if(!d)a=true;if(t){i.push({file:g,results:m})}else{for(const f of m){if(f.valid){if(!k)k=f.name;console.log(`\u2705 workflow "${f.name}" \u2014 validation passed`)}else{console.log(`\u274C workflow "${f.name}" \u2014 validation failed`);for(const u of f.errors){console.log(` - ${u.path}: ${u.message}`)}}console.log()}}}if(t){const g=i.flatMap(d=>d.results);console.log(JSON.stringify({success:!a,workflows:g},null,2))}if(!t&&!a){const g=l??k;if(g){const d={commandId:"workflow.validate",params:{name:g},resultCount:1};const m=(0,$.generateNextActions)(d);const c=(0,$.formatActions)(m);if(c)console.log(c)}}if(a)process.exit(1)}function ae(e){return{task_id:e.id,kind:e.kind,before:{status:e.before.status,error:e.before.error,completed_at:e.before.completedAt,duration_ms:e.before.durationMs},after:{status:e.after.status,error:e.after.error,completed_at:e.after.completedAt,duration_ms:e.after.durationMs},reason:e.reason}}function fe(e){return{instance_id:e.instanceId,before:e.before,after:e.after}}function T(e,o,t,n=[]){const s=e.parentChanges.map(ae);const l=e.auditSnapshotChanges.map(fe);const a=[...e.errors,...n];if(t){console.log(JSON.stringify({success:true,exec_id:e.runId,mode:o,dry_run:o==="dry-run",applied:o==="apply",run_status:e.runStatus,changes:s,audit_card_changes:l,...a.length>0?{errors:a}:{}},null,2));return}console.log(`\u{1F527} Workflow repair (${o}): ${e.runId}`);console.log(` Run status: ${e.runStatus}`);if(s.length===0&&l.length===0){console.log(" No parent lifecycle changes needed.")}else{for(const i of s){console.log(` ${o==="apply"?"Updated":"Would update"} ${i.task_id} (${i.kind}): ${i.before.status} \u2192 ${i.after.status}`);console.log(` Reason: ${i.reason}`)}for(const i of l){console.log(` ${o==="apply"?"Updated":"Would update"} audit card ${i.instance_id}: ${i.before.phase??"(unknown)"} \u2192 ${i.after.phase}`)}}for(const i of a){console.error(` Warning: ${i}`)}}function Y(e,o){if(e.id!==o){throw new Error(`Execution file ID mismatch: requested ${o}, found ${e.id||"(missing)"}`)}if(!e.workflow_id||!e.workflow_file||!e.created_at||!e.updated_at||typeof e.current_node!=="string"||!Array.isArray(e.completed_nodes)||!e.state||typeof e.state!=="object"||!e.initial_state||typeof e.initial_state!=="object"){throw new Error(`Run "${o}" is incomplete and cannot be repaired`)}}function ue(e,o,t,n){const s=e[0];if(!s){const r="Usage: workflow repair <run-id> [--dry-run|--apply]";if(o)console.log(JSON.stringify({success:false,error:r}));else console.error(`\u274C ${r}`);process.exit(1)}if(t&&n){const r="Choose either --dry-run or --apply, not both";if(o)console.log(JSON.stringify({success:false,error:r}));else console.error(`\u274C ${r}`);process.exit(1)}const l=F();const a=(0,W.hashWorkspaceId)(l);const i=new B.TaskRepository;const k=new I.RunManager(void 0,l);const g=new V.WorkflowEngine;let d;let m;const c=r=>G(r,g);const f=()=>{d??(d=new W.UiInstanceStore(i.resolveDbPath()));return d};const u=()=>{m??(m=(0,W.openUiInstanceReadStore)(i.resolveDbPath()));return m};const w=(0,R.createWorkflowPersistenceProjection)({taskRepo:i,workspaceId:a,resolveWorkflowSpec:c,getUiInstanceStore:f});const p={taskRepo:i,workspaceId:a,resolveWorkflowSpec:c,getUiInstanceStore:u};try{const r=k.readRun(s);if(!r)throw new Error(`Execution not found: ${s}`);Y(r,s);if(!(0,K.deriveWorkflowParentLifecycle)(r)){throw new Error(`Run "${s}" has an unsupported or insufficient persisted status: ${r.status??"pending"}`)}if(!t){T((0,R.previewWorkflowPersistenceProjection)(p,r),"dry-run",o);return}const h=k.withRunLock(s,_=>{Y(_,s);if(!(0,K.deriveWorkflowParentLifecycle)(_)){throw new Error(`Run "${s}" has an unsupported or insufficient persisted status: ${_.status??"pending"}`)}const x=(0,R.previewWorkflowPersistenceProjection)(p,_);const b=w(_);return{preview:x,projection:b}});T(h.preview,"apply",o,h.projection.errors)}catch(r){const h=r instanceof Error?r.message:String(r);if(o)console.log(JSON.stringify({success:false,error:h}));else console.error(`\u274C ${h}`);process.exit(1)}finally{d?.close();m?.close()}}async function de(e,o,t,n){const s=e[0];const l=e.slice(1);const a=F();const i=new B.TaskRepository;let k;const g=(0,R.createWorkflowPersistenceProjection)({taskRepo:i,workspaceId:(0,W.hashWorkspaceId)(a),resolveWorkflowSpec:c=>G(c,n),getUiInstanceStore:()=>{k??(k=new W.UiInstanceStore(i.resolveDbPath()));return k}});const d=new I.RunManager(void 0,a,{onRunPersisted:g});const{paths:m}=O();switch(s){case"start":{const c=l.filter((r,h)=>!r.startsWith("--")&&l[h-1]!=="--set");const f=c[0];if(!f){const r="Usage: workflow run start <id|file> [--set key=value ...]";if(t){console.log(JSON.stringify({success:false,error:r}))}else{console.error(`\u274C ${r}`)}process.exit(1)}let u;try{u=A(f,m,n)}catch(r){const h=r.message;if(t){console.log(JSON.stringify({success:false,error:h}))}else{console.error(`\u274C ${h}`)}process.exit(1)}const w=(0,I.parseSetArgs)(o);const p=o.includes("--auto");try{const r=d.start(u.filePath,u.workflowId??c[1],Object.keys(w).length>0?w:void 0);if(p){if(!t){console.log(`\u2705 Created execution: ${r.id}`);console.log(` Workflow: ${r.workflow_id} (${u.filePath})`)}const h=await d.runAuto(r.id);q(h,t);break}if(t){console.log(JSON.stringify({success:true,exec_id:r.id,run:r},null,2))}else{console.log(`\u2705 Created execution: ${r.id}`);console.log(` Workflow: ${r.workflow_id} (${u.filePath})`);console.log(` State keys: ${Object.keys(r.state).join(", ")||"(none)"}`);console.log(` File: ${y.join(".crewx/workflow-runs",r.id+".json")}`);let h=null;try{const P=N(n,m);const C=P.find(v=>v.id===r.workflow_id);if(C){const v=H(C.spec.nodes);if(v.length>0){console.log(` Flow: ${v.join(" \u2192 ")}`)}const j=Object.keys(C.spec.nodes);h=j[0]??null}}catch{}const _={commandId:"workflow.run.start",params:{name:r.workflow_id},resultIds:h?[r.id,h]:[r.id],resultCount:1};const x=(0,$.generateNextActions)(_);const b=(0,$.formatActions)(x);if(b)console.log(b)}}catch(r){const h=r.message;if(t){console.log(JSON.stringify({success:false,error:h}))}else{console.error(`\u274C ${h}`)}process.exit(1)}break}case"state":{const c=l.filter((r,h)=>!r.startsWith("--")&&l[h-1]!=="--set");const f=c[0];if(!f){const r="Usage: workflow run state <exec-id> [--set key=value ...]";if(t){console.log(JSON.stringify({success:false,error:r}))}else{console.error(`\u274C ${r}`)}process.exit(1)}const u=d.loadRun(f);if(!u){const r=`Execution not found: ${f}`;if(t){console.log(JSON.stringify({success:false,error:r}))}else{console.error(`\u274C ${r}`)}process.exit(1)}const w=(0,I.parseSetArgs)(o);const p=Object.entries(w).filter(([,r])=>r==="-").map(([r])=>r);if(p.length>0&&!process.stdin.isTTY){const r=await(0,I.readStdin)();let h;try{h=JSON.parse(r)}catch{h=r}for(const _ of p){w[_]=h}}if(Object.keys(w).length>0){Object.assign(u.state,w);d.saveRun(u)}if(t){console.log(JSON.stringify({success:true,exec_id:f,state:u.state},null,2))}else{if(Object.keys(w).length>0){console.log(`\u2705 Updated state for ${f}`)}console.log(JSON.stringify(u.state,null,2));const r=we(u,n);const h={commandId:"workflow.run.state",params:{execId:f},resultIds:r?[r]:[],resultCount:Object.keys(u.state).length};const _=(0,$.generateNextActions)(h);const x=(0,$.formatActions)(_);if(x)console.log(x)}break}case"node":{const c=l.filter(p=>!p.startsWith("--"));const f=c[0];const u=c[1];if(!f||!u){const p="Usage: workflow run node <exec-id> <node-id> [--no-exec]";if(t){console.log(JSON.stringify({success:false,error:p}))}else{console.error(`\u274C ${p}`)}process.exit(1)}const w=o.includes("--no-exec");try{const p=w?d.moveNode(f,u):await d.executeNode(f,u);const r=te(p,u,d.getNodeType(p.workflow_file,p.workflow_id,u),w);if(t){console.log(JSON.stringify({success:true,exec_id:f,current_node:p.current_node,completed_nodes:p.completed_nodes,status:p.status},null,2))}else{if(p.status==="completed"){console.log(`Workflow completed.`)}else{console.log(`\u2705 Node updated: ${u}${w?" (move only)":""}`);if(p.completed_nodes.length>0){console.log(` Completed: ${p.completed_nodes.join(" \u2192 ")}`)}console.log(` Current: ${p.current_node}`);if(r){console.log(` Status: ${r}`)}}let h=null;try{const{paths:P}=O();const C=N(n,P);const v=C.find(j=>j.id===p.workflow_id);if(v){h=X(p.current_node,u,v.spec.nodes)}}catch{}const _={commandId:"workflow.run.node",params:{execId:f,nodeId:u},resultIds:h?[h]:[],resultCount:1};const x=(0,$.generateNextActions)(_);const b=(0,$.formatActions)(x);if(b)console.log(b)}}catch(p){const r=p.message;if(t){console.log(JSON.stringify({success:false,error:r}))}else{console.error(`\u274C ${r}`);const h={commandId:"workflow.run.node",params:{execId:f,nodeId:u},resultIds:[],resultCount:0};const _=(0,$.generateNextActions)(h);const x=(0,$.formatActions)(_);if(x)console.log(x)}process.exit(1)}break}case"reset":{const c=l.filter(u=>!u.startsWith("--"));const f=c[0];if(!f){const u="Usage: workflow run reset <exec-id>";if(t){console.log(JSON.stringify({success:false,error:u}))}else{console.error(`\u274C ${u}`)}process.exit(1)}try{const u=d.reset(f);if(t){console.log(JSON.stringify({success:true,exec_id:f,state:u.state},null,2))}else{console.log(`\u2705 Reset execution: ${f}`);console.log(` State restored to initial values`)}}catch(u){const w=u.message;if(t){console.log(JSON.stringify({success:false,error:w}))}else{console.error(`\u274C ${w}`)}process.exit(1)}break}case"retry":{const c=l.filter(w=>!w.startsWith("--"));const f=c[0];if(!f){const w="Usage: workflow run retry <exec-id> [--no-run]";if(t){console.log(JSON.stringify({success:false,error:w}))}else{console.error(`\u274C ${w}`)}process.exit(1)}const u=o.includes("--no-run");try{const w=d.retry(f,{autoRun:!u});if(u){if(t){console.log(JSON.stringify({success:true,exec_id:w.id,current_node:w.current_node,retry_count:w.retry_count,status:w.status},null,2))}else{console.log(`\u2705 Retry transition: ${f}`);console.log(` Resume node: ${w.current_node}`);console.log(` Retry count: ${w.retry_count}`);console.log(` (state transition only; run "workflow run auto ${f}" to execute \u2014 re-runs the node, safe only if idempotent)`)}}else{const p=await d.runAuto(w.id);q(p,t);if(p.outcome==="failed")process.exit(1)}}catch(w){const p=w.message;if(t){console.log(JSON.stringify({success:false,error:p}))}else{console.error(`\u274C ${p}`)}process.exit(1)}break}case"list":{const c=d.listRuns();if(t){console.log(JSON.stringify({success:true,runs:c.map(f=>({id:f.id,workflow_id:f.workflow_id,workflow_file:f.workflow_file,created_at:f.created_at,current_node:f.current_node,completed_nodes:f.completed_nodes}))},null,2))}else{if(c.length===0){console.log("\u2139\uFE0F No workflow runs found.");return}console.log(`\u{1F4CB} Workflow runs (${c.length})`);for(const f of c){const u=f.current_node||"(not started)";const w=f.completed_nodes?.length||0;console.log(` ${f.id}`);console.log(` \u{1F4C1} ${f.workflow_file} | node: ${u} | completed: ${w}`)}}break}case"auto":{const c=l.filter(u=>!u.startsWith("--"));const f=c[0];if(!f){const u="Usage: workflow run auto <exec-id>";if(t){console.log(JSON.stringify({success:false,error:u}))}else{console.error(`\u274C ${u}`)}process.exit(1)}try{const u=await d.runAuto(f);q(u,t);if(u.outcome==="failed")process.exit(1)}catch(u){const w=u.message;if(t){console.log(JSON.stringify({success:false,error:w}))}else{console.error(`\u274C ${w}`)}process.exit(1)}break}default:{const c=`Unknown run subcommand: ${s||"(none)"}
54
- Usage: workflow run <start|state|node|auto|reset|retry|list> [args...]`;if(t){console.log(JSON.stringify({success:false,error:c}))}else{console.error(`\u274C ${c}`)}process.exit(1)}}}function q(e,o){const{run:t,outcome:n,reason:s,executed:l}=e;const a=t.status;if(o){console.log(JSON.stringify({success:n!=="failed",exec_id:t.id,status:a,outcome:n,current_node:t.current_node,completed_nodes:t.completed_nodes,executed:l,...s?{reason:s}:{}},null,2));return}const i=n==="failed"?"\u274C":n==="paused"?"\u23F8\uFE0F":"\u2705";console.log(`${i} Auto run: ${t.id}`);if(l.length>0){console.log(` Executed: ${l.join(" \u2192 ")}`)}console.log(` Outcome: ${n}${s?` (${s})`:""}`);console.log(` Current: ${t.current_node||"(none)"}`);console.log(` Status: ${a}`)}function X(e,o,t){const n=t[o];if(!n)return null;switch(n.type){case"agent_task":case"skill_task":case"shell_task":case"expression":return typeof n.next==="string"?n.next:null;case"branch":return e&&e!==o?e:null;case"end":case"parallel":case"join":return null;default:return null}}function H(e){const o=[];const t=Object.keys(e);if(t.length===0)return o;let n=t[0];const s=new Set;while(n&&!s.has(n)){s.add(n);o.push(n);const l=e[n];if(!l||l.type==="end")break;if(typeof l.next==="string"){n=l.next}else{o.push("...");break}}return o}function we(e,o){try{const{paths:t}=O();const n=N(o,t);const s=n.find(i=>i.id===e.workflow_id);if(!s)return null;const l=e.current_node;if(!l){const i=Object.keys(s.spec.nodes);return i[0]??null}const a=s.spec.nodes[l];if(!a)return null;if(typeof a.next==="string")return a.next;return null}catch{return null}}function pe(e,o,t){try{const{paths:n}=O();const s=N(t,n);const l=s.find(i=>i.id===e.workflow_id);if(!l)return null;const a=l.spec.nodes[o];return a?.agent??null}catch{return null}}async function L(e){const o=e??process.argv.slice(2);const{positional:t,options:n}=oe(o);const[s,...l]=t;if(n.version){console.log(E);return}if(n.help||s==="help"||!s){U();return}const a=!!n.json;const i=new V.WorkflowEngine;switch(s){case"list":case"ls":ie(i,a);break;case"show":{const k=l[0];if(!k){console.error("Error: workflow name is required\nUsage: workflow show <id|file> [--mermaid]");process.exit(1)}le(i,k,a,!!n.mermaid);break}case"validate":{const k=l[0];ce(i,k,a);break}case"run":{await de(l,o,a,i);break}case"repair":{ue(l,a,!!n.apply,!!n["dry-run"]);break}case"usage":ne();break;default:console.error(`Unknown subcommand: ${s}`);U();process.exit(1)}}if(require.main===module){M().then(e=>{const o=e?{skillVersion:e.getOwnSkillVersion(__dirname)}:{};return e?e.run("crewx/workflow",()=>L(),o):L()}).catch(e=>{console.error(e.message);process.exit(1)})}
52
+ `+g.map(p=>` workflow show ${p.id}`).join("\n"))}}}catch(w){const g=w.message;if(t){console.log(JSON.stringify({success:false,error:g}))}else{console.error(`\u274C ${g}`)}process.exit(1)}if(!u){const w=`Workflow "${o}" not found in: ${s.join(", ")}`;if(t){console.log(JSON.stringify({success:false,error:w}))}else{console.error(`\u274C ${w}`)}process.exit(1)}if(n){console.log(e.toMermaid(u.spec));return}const{spec:i}=u;const y=i.metadata??{};const k=i.nodes??{};const m=i.state?Object.keys(i.state):[];if(t){console.log(JSON.stringify({success:true,workflow:{id:u.id,name:y.name??u.id,description:y.description??"",timeout:i.timeout??null,max_iterations:i.max_iterations??null,state:m,node_count:Object.keys(k).length,nodes:Object.entries(k).map(([w,g])=>({id:w,type:g.type,agent:g.agent??null,next:g.next??null,branches:g.branches??null,join:g.join??null}))}},null,2))}else{const w=y.description?` \u2014 ${y.description}`:"";console.log(`\u{1F4C4} ${u.id}${w}`);console.log(` \u{1F4C1} ${u.filePath}`);const g=[];if(i.timeout)g.push(`timeout: ${i.timeout}s`);if(i.max_iterations)g.push(`max_iterations: ${i.max_iterations}`);if(g.length>0)console.log(` ${g.join(" | ")}`);if(m.length>0){console.log(`
53
+ State: ${m.join(", ")}`)}const p=ce(k);if(p.length>0){console.log(`
54
+ Flow:`);p.forEach(f=>console.log(f))}const c={commandId:"workflow.show",params:{name:o},resultCount:Object.keys(k).length};const a=(0,x.generateNextActions)(c);const d=(0,x.formatActions)(a);if(d)console.log(d)}}function fe(e,o,t){const{paths:n}=b();let s;let l;if(o){try{const k=P(o,n,e);if(!v.existsSync(k.filePath)){const m=`File not found: ${k.filePath}`;if(t){console.log(JSON.stringify({success:false,error:m,workflows:[]}))}else{console.error(`\u274C ${m}`)}process.exit(2)}s=[k.filePath];if(!o.endsWith(".yaml")&&!o.endsWith(".yml")){l=o}}catch(k){const m=k.message;if(t){console.log(JSON.stringify({success:false,error:m,workflows:[]}))}else{console.error(`\u274C ${m}`)}process.exit(2)}}else{s=[];for(const k of n){const m=$.resolve(process.cwd(),k);if(!v.existsSync(m)||!v.statSync(m).isDirectory())continue;const w=v.readdirSync(m).filter(g=>g.endsWith(".yaml")||g.endsWith(".yml")).map(g=>$.join(m,g));s.push(...w)}}if(s.length===0){const k="No workflow YAML files found";if(t){console.log(JSON.stringify({success:true,message:k,workflows:[]}))}else{console.log(`\u2139\uFE0F ${k}`)}return}let u=false;const i=[];let y;for(const k of s){const{success:m,results:w,error:g}=e.validateFile(k);if(g){u=true;if(t){i.push({file:k,results:[{name:$.basename(k),valid:false,errors:[{path:k,message:g}]}]})}else{console.error(`\u274C ${k}: ${g}
55
+ `)}continue}if(!m)u=true;if(t){i.push({file:k,results:w})}else{for(const p of w){if(p.valid){if(!y)y=p.name;console.log(`\u2705 workflow "${p.name}" \u2014 validation passed`)}else{console.log(`\u274C workflow "${p.name}" \u2014 validation failed`);for(const c of p.errors){console.log(` - ${c.path}: ${c.message}`)}}console.log()}}}if(t){const k=i.flatMap(m=>m.results);console.log(JSON.stringify({success:!u,workflows:k},null,2))}if(!t&&!u){const k=l??y;if(k){const m={commandId:"workflow.validate",params:{name:k},resultCount:1};const w=(0,x.generateNextActions)(m);const g=(0,x.formatActions)(w);if(g)console.log(g)}}if(u)process.exit(1)}function de(e){return{task_id:e.id,kind:e.kind,before:{status:e.before.status,error:e.before.error,completed_at:e.before.completedAt,duration_ms:e.before.durationMs},after:{status:e.after.status,error:e.after.error,completed_at:e.after.completedAt,duration_ms:e.after.durationMs},reason:e.reason}}function we(e){return{instance_id:e.instanceId,before:e.before,after:e.after}}function Y(e,o,t,n=[]){const s=e.parentChanges.map(de);const l=e.auditSnapshotChanges.map(we);const u=[...e.errors,...n];if(t){console.log(JSON.stringify({success:true,exec_id:e.runId,mode:o,dry_run:o==="dry-run",applied:o==="apply",run_status:e.runStatus,changes:s,audit_card_changes:l,...u.length>0?{errors:u}:{}},null,2));return}console.log(`\u{1F527} Workflow repair (${o}): ${e.runId}`);console.log(` Run status: ${e.runStatus}`);if(s.length===0&&l.length===0){console.log(" No parent lifecycle changes needed.")}else{for(const i of s){console.log(` ${o==="apply"?"Updated":"Would update"} ${i.task_id} (${i.kind}): ${i.before.status} \u2192 ${i.after.status}`);console.log(` Reason: ${i.reason}`)}for(const i of l){console.log(` ${o==="apply"?"Updated":"Would update"} audit card ${i.instance_id}: ${i.before.phase??"(unknown)"} \u2192 ${i.after.phase}`)}}for(const i of u){console.error(` Warning: ${i}`)}}function H(e,o){if(e.id!==o){throw new Error(`Execution file ID mismatch: requested ${o}, found ${e.id||"(missing)"}`)}if(!e.workflow_id||!e.workflow_file||!e.created_at||!e.updated_at||typeof e.current_node!=="string"||!Array.isArray(e.completed_nodes)||!e.state||typeof e.state!=="object"||!e.initial_state||typeof e.initial_state!=="object"){throw new Error(`Run "${o}" is incomplete and cannot be repaired`)}}function pe(e,o,t,n){const s=e[0];if(!s){const f="Usage: workflow repair <run-id> [--dry-run|--apply]";if(o)console.log(JSON.stringify({success:false,error:f}));else console.error(`\u274C ${f}`);process.exit(1)}if(t&&n){const f="Choose either --dry-run or --apply, not both";if(o)console.log(JSON.stringify({success:false,error:f}));else console.error(`\u274C ${f}`);process.exit(1)}const l=F();const u=(0,W.hashWorkspaceId)(l);const i=new T.TaskRepository;const y=new I.RunManager(void 0,l);const k=new G.WorkflowEngine;let m;let w;const g=f=>q(f,k);const p=()=>{m??(m=new W.UiInstanceStore(i.resolveDbPath()));return m};const c=()=>{w??(w=(0,W.openUiInstanceReadStore)(i.resolveDbPath()));return w};const a=(0,j.createWorkflowPersistenceProjection)({taskRepo:i,workspaceId:u,resolveWorkflowSpec:g,getUiInstanceStore:p});const d={taskRepo:i,workspaceId:u,resolveWorkflowSpec:g,getUiInstanceStore:c};try{const f=y.readRun(s);if(!f)throw new Error(`Execution not found: ${s}`);H(f,s);if(!(0,K.deriveWorkflowParentLifecycle)(f)){throw new Error(`Run "${s}" has an unsupported or insufficient persisted status: ${f.status??"pending"}`)}if(!t){Y((0,j.previewWorkflowPersistenceProjection)(d,f),"dry-run",o);return}const r=y.withRunLock(s,h=>{H(h,s);if(!(0,K.deriveWorkflowParentLifecycle)(h)){throw new Error(`Run "${s}" has an unsupported or insufficient persisted status: ${h.status??"pending"}`)}const _=(0,j.previewWorkflowPersistenceProjection)(d,h);const S=a(h);return{preview:_,projection:S}});Y(r.preview,"apply",o,r.projection.errors)}catch(f){const r=f instanceof Error?f.message:String(f);if(o)console.log(JSON.stringify({success:false,error:r}));else console.error(`\u274C ${r}`);process.exit(1)}finally{m?.close();w?.close()}}async function ge(e,o,t,n){const s=e[0];const l=e.slice(1);const u=F();const i=new T.TaskRepository;const y=(0,W.hashWorkspaceId)(u);let k;const m=(0,j.createWorkflowPersistenceProjection)({taskRepo:i,workspaceId:y,resolveWorkflowSpec:p=>q(p,n),getUiInstanceStore:()=>{k??(k=new W.UiInstanceStore(i.resolveDbPath()));return k}});const w=new I.RunManager(void 0,u,{onRunPersisted:m});const{paths:g}=b();switch(s){case"start":{const p=l.filter((r,h)=>!r.startsWith("--")&&l[h-1]!=="--set");const c=p[0];if(!c){const r="Usage: workflow run start <id|file> [--set key=value ...]";if(t){console.log(JSON.stringify({success:false,error:r}))}else{console.error(`\u274C ${r}`)}process.exit(1)}let a;try{a=P(c,g,n)}catch(r){const h=r.message;if(t){console.log(JSON.stringify({success:false,error:h}))}else{console.error(`\u274C ${h}`)}process.exit(1)}const d=(0,I.parseSetArgs)(o);const f=o.includes("--auto");try{const r=w.start(a.filePath,a.workflowId??p[1],Object.keys(d).length>0?d:void 0);const h=(0,te.createWorkflowRunAnchor)({taskRepo:i,run:r,workspaceId:y});if(f){if(!t){console.log(`\u2705 Created execution: ${r.id}`);console.log(` Workflow: ${r.workflow_id} (${a.filePath})`)}const _=await w.runAuto(r.id);B(_,t);break}if(t){console.log(JSON.stringify({success:true,exec_id:r.id,task_id:h,run:r},null,2))}else{console.log(`\u2705 Created execution: ${r.id}`);console.log(` Workflow: ${r.workflow_id} (${a.filePath})`);console.log(` State keys: ${Object.keys(r.state).join(", ")||"(none)"}`);console.log(` File: ${$.join(".crewx/workflow-runs",r.id+".json")}`);console.log(` Task: ${h}`);let _=null;try{const J=O(n,g);const N=J.find(C=>C.id===r.workflow_id);if(N){const C=Q(N.spec.nodes);if(C.length>0){console.log(` Flow: ${C.join(" \u2192 ")}`)}const Z=Object.keys(N.spec.nodes);_=Z[0]??null}}catch{}const S={commandId:"workflow.run.start",params:{name:r.workflow_id},resultIds:_?[r.id,_]:[r.id],resultCount:1};const A=(0,x.generateNextActions)(S);const R=(0,x.formatActions)(A);if(R)console.log(R)}}catch(r){const h=r.message;if(t){console.log(JSON.stringify({success:false,error:h}))}else{console.error(`\u274C ${h}`)}process.exit(1)}break}case"approve":case"reject":{const p=l.filter(r=>!r.startsWith("--"));const c=p[0];const a=s==="approve"?"approve":"reject";const d=r=>{if(t){console.log(JSON.stringify({success:false,error:r}))}else{console.error(`\u274C ${r}`)}process.exit(1)};if(!c)return d(`Usage: workflow run ${a} <exec-id>${a==="reject"?" [--reason=<text>]":""}`);if(process.env["CREWX_AGENT_ID"]){return d("Approval decisions are made by a person. Agent processes cannot approve or reject a run.")}const f=X(o).options["reason"];try{const r=w.loadRun(c);if(!r)throw new Error(`Execution not found: ${c}`);const h=q(r,n);if(!h)throw new Error(`Workflow definition for run ${c} was not found`);const _=w.decideApproval(c,a,{spec:h,actor:"cli",reason:typeof f==="string"?f:void 0});if(t){console.log(JSON.stringify({success:true,exec_id:c,decision:a,current_node:_.current_node,completed_nodes:_.completed_nodes,status:_.status},null,2))}else{console.log(`\u2705 ${a==="approve"?"Approved":"Rejected"}: ${c}`);console.log(` Current: ${_.current_node}`);console.log(` Continue: workflow run auto ${c}`)}}catch(r){d(r instanceof Error?r.message:String(r))}break}case"state":{const p=l.filter((r,h)=>!r.startsWith("--")&&l[h-1]!=="--set");const c=p[0];if(!c){const r="Usage: workflow run state <exec-id> [--set key=value ...]";if(t){console.log(JSON.stringify({success:false,error:r}))}else{console.error(`\u274C ${r}`)}process.exit(1)}const a=w.loadRun(c);if(!a){const r=`Execution not found: ${c}`;if(t){console.log(JSON.stringify({success:false,error:r}))}else{console.error(`\u274C ${r}`)}process.exit(1)}const d=(0,I.parseSetArgs)(o);const f=Object.entries(d).filter(([,r])=>r==="-").map(([r])=>r);if(f.length>0&&!process.stdin.isTTY){const r=await(0,I.readStdin)();let h;try{h=JSON.parse(r)}catch{h=r}for(const _ of f){d[_]=h}}if(Object.keys(d).length>0){Object.assign(a.state,d);w.saveRun(a)}if(t){console.log(JSON.stringify({success:true,exec_id:c,state:a.state},null,2))}else{if(Object.keys(d).length>0){console.log(`\u2705 Updated state for ${c}`)}console.log(JSON.stringify(a.state,null,2));const r=ke(a,n);const h={commandId:"workflow.run.state",params:{execId:c},resultIds:r?[r]:[],resultCount:Object.keys(a.state).length};const _=(0,x.generateNextActions)(h);const S=(0,x.formatActions)(_);if(S)console.log(S)}break}case"node":{const p=l.filter(f=>!f.startsWith("--"));const c=p[0];const a=p[1];if(!c||!a){const f="Usage: workflow run node <exec-id> <node-id> [--no-exec]";if(t){console.log(JSON.stringify({success:false,error:f}))}else{console.error(`\u274C ${f}`)}process.exit(1)}const d=o.includes("--no-exec");try{const f=d?w.moveNode(c,a):await w.executeNode(c,a);const r=re(f,a,w.getNodeType(f.workflow_file,f.workflow_id,a),d);if(t){console.log(JSON.stringify({success:true,exec_id:c,current_node:f.current_node,completed_nodes:f.completed_nodes,status:f.status},null,2))}else{if(f.status==="completed"){console.log(`Workflow completed.`)}else{console.log(`\u2705 Node updated: ${a}${d?" (move only)":""}`);if(f.completed_nodes.length>0){console.log(` Completed: ${f.completed_nodes.join(" \u2192 ")}`)}console.log(` Current: ${f.current_node}`);if(r){console.log(` Status: ${r}`)}}let h=null;try{const{paths:R}=b();const J=O(n,R);const N=J.find(C=>C.id===f.workflow_id);if(N){h=z(f.current_node,a,N.spec.nodes)}}catch{}const _={commandId:"workflow.run.node",params:{execId:c,nodeId:a},resultIds:h?[h]:[],resultCount:1};const S=(0,x.generateNextActions)(_);const A=(0,x.formatActions)(S);if(A)console.log(A)}}catch(f){const r=f.message;if(t){console.log(JSON.stringify({success:false,error:r}))}else{console.error(`\u274C ${r}`);const h={commandId:"workflow.run.node",params:{execId:c,nodeId:a},resultIds:[],resultCount:0};const _=(0,x.generateNextActions)(h);const S=(0,x.formatActions)(_);if(S)console.log(S)}process.exit(1)}break}case"reset":{const p=l.filter(a=>!a.startsWith("--"));const c=p[0];if(!c){const a="Usage: workflow run reset <exec-id>";if(t){console.log(JSON.stringify({success:false,error:a}))}else{console.error(`\u274C ${a}`)}process.exit(1)}try{const a=w.reset(c);if(t){console.log(JSON.stringify({success:true,exec_id:c,state:a.state},null,2))}else{console.log(`\u2705 Reset execution: ${c}`);console.log(` State restored to initial values`)}}catch(a){const d=a.message;if(t){console.log(JSON.stringify({success:false,error:d}))}else{console.error(`\u274C ${d}`)}process.exit(1)}break}case"retry":{const p=l.filter(d=>!d.startsWith("--"));const c=p[0];if(!c){const d="Usage: workflow run retry <exec-id> [--no-run]";if(t){console.log(JSON.stringify({success:false,error:d}))}else{console.error(`\u274C ${d}`)}process.exit(1)}const a=o.includes("--no-run");try{const d=w.retry(c,{autoRun:!a});if(a){if(t){console.log(JSON.stringify({success:true,exec_id:d.id,current_node:d.current_node,retry_count:d.retry_count,status:d.status},null,2))}else{console.log(`\u2705 Retry transition: ${c}`);console.log(` Resume node: ${d.current_node}`);console.log(` Retry count: ${d.retry_count}`);console.log(` (state transition only; run "workflow run auto ${c}" to execute \u2014 re-runs the node, safe only if idempotent)`)}}else{const f=await w.runAuto(d.id);B(f,t);if(f.outcome==="failed")process.exit(1)}}catch(d){const f=d.message;if(t){console.log(JSON.stringify({success:false,error:f}))}else{console.error(`\u274C ${f}`)}process.exit(1)}break}case"list":{const p=w.listRuns();if(t){console.log(JSON.stringify({success:true,runs:p.map(c=>({id:c.id,workflow_id:c.workflow_id,workflow_file:c.workflow_file,created_at:c.created_at,current_node:c.current_node,completed_nodes:c.completed_nodes}))},null,2))}else{if(p.length===0){console.log("\u2139\uFE0F No workflow runs found.");return}console.log(`\u{1F4CB} Workflow runs (${p.length})`);for(const c of p){const a=c.current_node||"(not started)";const d=c.completed_nodes?.length||0;console.log(` ${c.id}`);console.log(` \u{1F4C1} ${c.workflow_file} | node: ${a} | completed: ${d}`)}}break}case"auto":{const p=l.filter(a=>!a.startsWith("--"));const c=p[0];if(!c){const a="Usage: workflow run auto <exec-id>";if(t){console.log(JSON.stringify({success:false,error:a}))}else{console.error(`\u274C ${a}`)}process.exit(1)}try{const a=await w.runAuto(c);B(a,t);if(a.outcome==="failed")process.exit(1)}catch(a){const d=a.message;if(t){console.log(JSON.stringify({success:false,error:d}))}else{console.error(`\u274C ${d}`)}process.exit(1)}break}default:{const p=`Unknown run subcommand: ${s||"(none)"}
56
+ Usage: workflow run <start|state|node|auto|reset|retry|list> [args...]`;if(t){console.log(JSON.stringify({success:false,error:p}))}else{console.error(`\u274C ${p}`)}process.exit(1)}}}function B(e,o){const{run:t,outcome:n,reason:s,executed:l}=e;const u=t.status;if(o){console.log(JSON.stringify({success:n!=="failed",exec_id:t.id,status:u,outcome:n,current_node:t.current_node,completed_nodes:t.completed_nodes,executed:l,...s?{reason:s}:{}},null,2));return}const i=n==="failed"?"\u274C":n==="paused"?"\u23F8\uFE0F":"\u2705";console.log(`${i} Auto run: ${t.id}`);if(l.length>0){console.log(` Executed: ${l.join(" \u2192 ")}`)}console.log(` Outcome: ${n}${s?` (${s})`:""}`);console.log(` Current: ${t.current_node||"(none)"}`);console.log(` Status: ${u}`)}function z(e,o,t){const n=t[o];if(!n)return null;switch(n.type){case"agent_task":case"skill_task":case"shell_task":case"expression":return typeof n.next==="string"?n.next:null;case"branch":return e&&e!==o?e:null;case"end":case"parallel":case"join":return null;default:return null}}function Q(e){const o=[];const t=Object.keys(e);if(t.length===0)return o;let n=t[0];const s=new Set;while(n&&!s.has(n)){s.add(n);o.push(n);const l=e[n];if(!l||l.type==="end")break;if(typeof l.next==="string"){n=l.next}else{o.push("...");break}}return o}function ke(e,o){try{const{paths:t}=b();const n=O(o,t);const s=n.find(i=>i.id===e.workflow_id);if(!s)return null;const l=e.current_node;if(!l){const i=Object.keys(s.spec.nodes);return i[0]??null}const u=s.spec.nodes[l];if(!u)return null;if(typeof u.next==="string")return u.next;return null}catch{return null}}function he(e,o,t){try{const{paths:n}=b();const s=O(t,n);const l=s.find(i=>i.id===e.workflow_id);if(!l)return null;const u=l.spec.nodes[o];return u?.agent??null}catch{return null}}async function V(e){const o=e??process.argv.slice(2);const{positional:t,options:n}=X(o);const[s,...l]=t;if(n.version){console.log(U);return}if(n.help||s==="help"||!s){L();return}const u=!!n.json;const i=new G.WorkflowEngine;switch(s){case"list":case"ls":ae(i,u);break;case"show":{const y=l[0];if(!y){console.error("Error: workflow name is required\nUsage: workflow show <id|file> [--mermaid]");process.exit(1)}ue(i,y,u,!!n.mermaid);break}case"validate":{const y=l[0];fe(i,y,u);break}case"run":{await ge(l,o,u,i);break}case"repair":{pe(l,u,!!n.apply,!!n["dry-run"]);break}case"usage":le();break;default:console.error(`Unknown subcommand: ${s}`);L();process.exit(1)}}if(require.main===module){se().then(e=>{const o=e?{skillVersion:e.getOwnSkillVersion(__dirname)}:{};return e?e.run("crewx/workflow",()=>V(),o):V()}).catch(e=>{console.error(e.message);process.exit(1)})}
@@ -0,0 +1,4 @@
1
+ import type { RunExecution } from './types.js';
2
+ export declare const USER_CANCEL_REASON = "Cancelled by user";
3
+ export declare function isUserCancelledRun(run: Pick<RunExecution, 'error'>): boolean;
4
+
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.USER_CANCEL_REASON=void 0;exports.isUserCancelledRun=r;exports.USER_CANCEL_REASON="Cancelled by user";function r(e){return!e.error||e.error===exports.USER_CANCEL_REASON}
@@ -17,6 +17,10 @@ export type { ReconcileWorkflowParentTasksOptions, SyncWorkflowParentTaskLifecyc
17
17
  export { createWorkflowPersistenceProjection, previewWorkflowPersistenceProjection, } from './workflow-persistence';
18
18
  export type { WorkflowAuditSnapshotChange, WorkflowAuditSnapshotStore, WorkflowPersistenceProjection, WorkflowPersistenceProjectionOptions, WorkflowPersistenceProjectionResult, WorkflowPersistenceProjectionPreview, WorkflowPersistenceProjectionPreviewOptions, } from './workflow-persistence';
19
19
  export type { AutoRunResult, RunExecution, WorkflowAutoRunLease, WorkflowManualResume, WorkflowNode, WorkflowSpec, } from './types';
20
+ export { projectRunSteps, summarizeRunProgress } from './run-projection';
21
+ export type { RunProgressSummary, RunStepProjection, RunStepStatus, RunStepTaskLookup, } from './run-projection';
22
+ export { createWorkflowRunAnchor } from './run-anchor';
23
+ export type { CreateWorkflowRunAnchorOptions } from './run-anchor';
20
24
  export { buildWorkflowApprovalState } from './card-state';
21
25
  export type { WorkflowApprovalNodeState, WorkflowApprovalPhase, WorkflowApprovalState, } from './card-state';
22
26
 
@@ -1 +1 @@
1
- "use strict";var P=exports&&exports.__createBinding||(Object.create?(function(a,e,i,t){if(t===void 0)t=i;var n=Object.getOwnPropertyDescriptor(e,i);if(!n||("get"in n?!e.__esModule:n.writable||n.configurable)){n={enumerable:true,get:function(){return e[i]}}}Object.defineProperty(a,t,n)}):(function(a,e,i,t){if(t===void 0)t=i;a[t]=e[i]}));var W=exports&&exports.__setModuleDefault||(Object.create?(function(a,e){Object.defineProperty(a,"default",{enumerable:true,value:e})}):function(a,e){a["default"]=e});var b=exports&&exports.__importStar||(function(){var a=function(e){a=Object.getOwnPropertyNames||function(i){var t=[];for(var n in i)if(Object.prototype.hasOwnProperty.call(i,n))t[t.length]=n;return t};return a(e)};return function(e){if(e&&e.__esModule)return e;var i={};if(e!=null){for(var t=a(e),n=0;n<t.length;n++)if(t[n]!=="default")P(i,e,t[n])}W(i,e);return i}})();Object.defineProperty(exports,"__esModule",{value:true});exports.buildWorkflowApprovalState=exports.previewWorkflowPersistenceProjection=exports.createWorkflowPersistenceProjection=exports.WORKFLOW_PARENT_TASK_KINDS=exports.WORKFLOW_PARENT_ORPHAN_GRACE_MS=exports.WORKFLOW_PARENT_ORPHAN_ERROR=exports.syncWorkflowParentTaskLifecycle=exports.reconcileWorkflowParentTasks=exports.reconcileWorkflowParentRunFiles=exports.previewWorkflowParentRepair=exports.deriveWorkflowParentLifecycleDecision=exports.deriveWorkflowParentLifecycleChange=exports.deriveWorkflowParentLifecycle=exports.createWorkflowParentPersistSink=exports.normalizeRunStatus=exports.WORKFLOW_RUN_LEGACY_RECOVERY_GRACE_MS=exports.WORKFLOW_RUN_INTERRUPTED_ERROR=exports.RunManager=exports.WorkflowEngine=void 0;const E=require("./list-view");const m=b(require("fs"));const R=b(require("path"));const j=b(require("js-yaml"));const A=require("./mermaid");const g=require("./utils/shell-security");const k=require("./run-manager");const O=require("./retry-policy");const v={agent_task:["agent"],skill_task:["skill"],shell_task:["command"],parallel:["branches","join"],join:[],branch:["condition","branches"],approval:["description","on_approve","on_reject"],expression:["set","next"],end:[],error:[]};function w(a,e,i){if(i>10)return null;const t=a.replace("#/","").split("/");let n=e;for(const s of t){n=n?.[s];if(!n)return null}const r=n;if(r.$ref)return w(r.$ref,e,i+1);return r}function S(a){if(a===null||a===void 0)return"null";if(Array.isArray(a))return"array";return typeof a}function y(a,e,i,t,n,r){if(r>20||!e)return;if(e.$ref){const s=w(e.$ref,i,0);if(!s){n.push({path:t,message:`cannot resolve $ref "${e.$ref}"`});return}e=s}if(e.type){const s=S(a);const f=e.type==="integer"?s==="number"&&Number.isInteger(a):e.type===s;if(!f){n.push({path:t,message:`expected type "${e.type}", got "${s}"`});return}}if(typeof a==="number"){if(e.minimum!==void 0&&a<e.minimum){n.push({path:t,message:`number must be >= ${e.minimum}`})}if(e.maximum!==void 0&&a>e.maximum){n.push({path:t,message:`number must be <= ${e.maximum}`})}}if(e.enum){if(!e.enum.includes(a)){n.push({path:t,message:`value "${a}" not in enum [${e.enum.join(", ")}]`})}}if(e.type==="object"&&typeof a==="object"&&a!==null&&!Array.isArray(a)){const s=a;if(e.required){for(const f of e.required){if(!(f in s)){n.push({path:`${t}.${f}`,message:"required field missing"})}}}if(e.properties){for(const[f,o]of Object.entries(e.properties)){if(f in s){y(s[f],o,i,`${t}.${f}`,n,r+1)}}}if(e.additionalProperties&&typeof e.additionalProperties==="object"){const f=e.properties?Object.keys(e.properties):[];for(const o of Object.keys(s)){if(!f.includes(o)){y(s[o],e.additionalProperties,i,`${t}.${o}`,n,r+1)}}}}if(e.type==="array"&&Array.isArray(a)){if(e.items){a.forEach((s,f)=>{y(s,e.items,i,`${t}[${f}]`,n,r+1)})}}}function L(a,e,i,t){const n=v[e.type];if(!n)return;for(const r of n){if(!(r in e)){t.push({path:`${i}.${r}`,message:`"${e.type}" node requires "${r}" field`})}}if(e.type==="parallel"&&e.branches&&!Array.isArray(e.branches)){t.push({path:`${i}.branches`,message:'"parallel" node branches must be an array of node IDs'})}if(e.type==="branch"&&e.branches&&Array.isArray(e.branches)){t.push({path:`${i}.branches`,message:'"branch" node branches must be a Record (object), not an array'})}if(e.type==="branch"&&typeof e.condition==="string"){try{(0,k.validateSafeBranchCondition)(e.condition)}catch(r){t.push({path:`${i}.condition`,message:`unsafe branch condition: ${r.message}`})}}if(typeof e.fail_when==="string"){const r=["agent_task","skill_task","shell_task"];if(!r.includes(e.type)){t.push({path:`${i}.fail_when`,message:`"fail_when" is only supported on task nodes (${r.join(", ")}), not "${e.type}"`})}else{try{(0,k.validateSafeBranchCondition)(e.fail_when)}catch(s){t.push({path:`${i}.fail_when`,message:`unsafe fail_when condition: ${s.message}`})}}}if(e.retry!==void 0){if(!O.RETRYABLE_NODE_TYPES.includes(e.type)){t.push({path:`${i}.retry`,message:'"retry" is only supported on agent_task, skill_task, and shell_task nodes'})}else{const r=(0,O.parseRetryPolicy)(e);if(!r.ok){const s=`${i}.${r.path}`;if(!t.some(f=>f.path===s)){t.push({path:s,message:r.reason})}}}}if(e.type==="skill_task"&&e.skill!==void 0&&!g.SKILL_NAME_RE.test(e.skill)){t.push({path:`${i}.skill`,message:`skill name "${e.skill}" is invalid (must match ^[a-z0-9][a-z0-9-]*$, no path traversal)`})}if(e.type==="shell_task"&&e.command!==void 0){const r=(0,g.validateShellCommand)(e.command);if(!r.ok){t.push({path:`${i}.command`,message:r.error})}const s=(0,g.validateEnvKeys)(e.env);if(!s.ok){t.push({path:`${i}.env`,message:s.error})}}}function T(a,e){const i=a.length;const t=e.length;const n=Array.from({length:i+1},()=>Array(t+1).fill(0));for(let r=0;r<=i;r++)n[r][0]=r;for(let r=0;r<=t;r++)n[0][r]=r;for(let r=1;r<=i;r++){for(let s=1;s<=t;s++){n[r][s]=a[r-1]===e[s-1]?n[r-1][s-1]:1+Math.min(n[r-1][s],n[r][s-1],n[r-1][s-1])}}return n[i][t]}function K(a,e){let i=null;let t=Infinity;for(const n of e){const r=T(a,n);if(r<t&&r<=3){t=r;i=n}}return i}function F(a,e,i,t){const n=e.nodes;const r=new Set(Object.keys(n));const s=`workflows.${a}`;for(const[f,o]of Object.entries(n)){const c=`${s}.nodes.${f}`;if(o.next&&!r.has(o.next)){t.push({path:`${c}.next`,message:`"${o.next}" node not found`})}if(o.agent&&i&&!i[o.agent]){t.push({path:`${c}.agent`,message:`agent "${o.agent}" not found in agents section`})}if(o.type==="parallel"&&Array.isArray(o.branches)){o.branches.forEach((p,l)=>{if(!r.has(p)){t.push({path:`${c}.branches[${l}]`,message:`"${p}" node not found`})}});if(o.join&&!r.has(o.join)){t.push({path:`${c}.join`,message:`"${o.join}" node not found`})}}if(o.type==="branch"&&o.branches&&typeof o.branches==="object"&&!Array.isArray(o.branches)){for(const[p,l]of Object.entries(o.branches)){if(!r.has(l)){const _=K(l,r);let h=`"${l}" node not found`;if(_)h+=` (did you mean "${_}"?)`;t.push({path:`${c}.branches.${p}`,message:h})}}}if(o.default&&!r.has(o.default)){t.push({path:`${c}.default`,message:`"${o.default}" node not found`})}if(o.type==="approval"){if(o.on_approve&&!r.has(o.on_approve)){t.push({path:`${c}.on_approve`,message:`"${o.on_approve}" node not found`})}if(o.on_reject&&!r.has(o.on_reject)){t.push({path:`${c}.on_reject`,message:`"${o.on_reject}" node not found`})}}if(o.on_error?.fallback_node&&!r.has(o.on_error.fallback_node)){t.push({path:`${c}.on_error.fallback_node`,message:`"${o.on_error.fallback_node}" node not found`})}}}class M{constructor(){let e=R.join(__dirname,"..","workflow-schema.json");if(!m.existsSync(e)){e=R.join(__dirname,"..","..","workflow-schema.json")}this.schema=JSON.parse(m.readFileSync(e,"utf-8"))}loadWorkflows(e){return(0,E.loadWorkflowDirectory)(e)}validateFile(e){let i;try{i=m.readFileSync(e,"utf-8")}catch{return{success:false,results:[],error:`File not found or unreadable: ${e}`}}let t;try{t=j.load(i)}catch(f){const o=f instanceof Error?f.message:String(f);return{success:false,results:[],error:`YAML parse error: ${o}`}}if(!t||typeof t!=="object"){return{success:false,results:[],error:"Invalid YAML: document is empty or not an object"}}if(!t.workflows||typeof t.workflows!=="object"){return{success:true,results:[]}}const n=t.agents??null;const r=[];let s=false;for(const[f,o]of Object.entries(t.workflows)){const c=[];const p=`workflows.${f}`;y(o,this.schema.definitions["WorkflowSpec"],this.schema,p,c,0);if(o.nodes&&typeof o.nodes==="object"){for(const[l,_]of Object.entries(o.nodes)){if(_&&typeof _==="object"&&_.type){L(l,_,`${p}.nodes.${l}`,c)}}}F(f,o,n,c);if(c.length>0)s=true;r.push({name:f,valid:c.length===0,errors:c})}return{success:!s,results:r}}toMermaid(e){return(0,A.toMermaid)(e)}}exports.WorkflowEngine=M;var d=require("./run-manager");Object.defineProperty(exports,"RunManager",{enumerable:true,get:function(){return d.RunManager}});Object.defineProperty(exports,"WORKFLOW_RUN_INTERRUPTED_ERROR",{enumerable:true,get:function(){return d.WORKFLOW_RUN_INTERRUPTED_ERROR}});Object.defineProperty(exports,"WORKFLOW_RUN_LEGACY_RECOVERY_GRACE_MS",{enumerable:true,get:function(){return d.WORKFLOW_RUN_LEGACY_RECOVERY_GRACE_MS}});Object.defineProperty(exports,"normalizeRunStatus",{enumerable:true,get:function(){return d.normalizeRunStatus}});var u=require("./parent-task-sync");Object.defineProperty(exports,"createWorkflowParentPersistSink",{enumerable:true,get:function(){return u.createWorkflowParentPersistSink}});Object.defineProperty(exports,"deriveWorkflowParentLifecycle",{enumerable:true,get:function(){return u.deriveWorkflowParentLifecycle}});Object.defineProperty(exports,"deriveWorkflowParentLifecycleChange",{enumerable:true,get:function(){return u.deriveWorkflowParentLifecycleChange}});Object.defineProperty(exports,"deriveWorkflowParentLifecycleDecision",{enumerable:true,get:function(){return u.deriveWorkflowParentLifecycleDecision}});Object.defineProperty(exports,"previewWorkflowParentRepair",{enumerable:true,get:function(){return u.previewWorkflowParentRepair}});Object.defineProperty(exports,"reconcileWorkflowParentRunFiles",{enumerable:true,get:function(){return u.reconcileWorkflowParentRunFiles}});Object.defineProperty(exports,"reconcileWorkflowParentTasks",{enumerable:true,get:function(){return u.reconcileWorkflowParentTasks}});Object.defineProperty(exports,"syncWorkflowParentTaskLifecycle",{enumerable:true,get:function(){return u.syncWorkflowParentTaskLifecycle}});Object.defineProperty(exports,"WORKFLOW_PARENT_ORPHAN_ERROR",{enumerable:true,get:function(){return u.WORKFLOW_PARENT_ORPHAN_ERROR}});Object.defineProperty(exports,"WORKFLOW_PARENT_ORPHAN_GRACE_MS",{enumerable:true,get:function(){return u.WORKFLOW_PARENT_ORPHAN_GRACE_MS}});Object.defineProperty(exports,"WORKFLOW_PARENT_TASK_KINDS",{enumerable:true,get:function(){return u.WORKFLOW_PARENT_TASK_KINDS}});var $=require("./workflow-persistence");Object.defineProperty(exports,"createWorkflowPersistenceProjection",{enumerable:true,get:function(){return $.createWorkflowPersistenceProjection}});Object.defineProperty(exports,"previewWorkflowPersistenceProjection",{enumerable:true,get:function(){return $.previewWorkflowPersistenceProjection}});var q=require("./card-state");Object.defineProperty(exports,"buildWorkflowApprovalState",{enumerable:true,get:function(){return q.buildWorkflowApprovalState}});
1
+ "use strict";var $=exports&&exports.__createBinding||(Object.create?(function(a,e,i,r){if(r===void 0)r=i;var n=Object.getOwnPropertyDescriptor(e,i);if(!n||("get"in n?!e.__esModule:n.writable||n.configurable)){n={enumerable:true,get:function(){return e[i]}}}Object.defineProperty(a,r,n)}):(function(a,e,i,r){if(r===void 0)r=i;a[r]=e[i]}));var j=exports&&exports.__setModuleDefault||(Object.create?(function(a,e){Object.defineProperty(a,"default",{enumerable:true,value:e})}):function(a,e){a["default"]=e});var b=exports&&exports.__importStar||(function(){var a=function(e){a=Object.getOwnPropertyNames||function(i){var r=[];for(var n in i)if(Object.prototype.hasOwnProperty.call(i,n))r[r.length]=n;return r};return a(e)};return function(e){if(e&&e.__esModule)return e;var i={};if(e!=null){for(var r=a(e),n=0;n<r.length;n++)if(r[n]!=="default")$(i,e,r[n])}j(i,e);return i}})();Object.defineProperty(exports,"__esModule",{value:true});exports.buildWorkflowApprovalState=exports.createWorkflowRunAnchor=exports.summarizeRunProgress=exports.projectRunSteps=exports.previewWorkflowPersistenceProjection=exports.createWorkflowPersistenceProjection=exports.WORKFLOW_PARENT_TASK_KINDS=exports.WORKFLOW_PARENT_ORPHAN_GRACE_MS=exports.WORKFLOW_PARENT_ORPHAN_ERROR=exports.syncWorkflowParentTaskLifecycle=exports.reconcileWorkflowParentTasks=exports.reconcileWorkflowParentRunFiles=exports.previewWorkflowParentRepair=exports.deriveWorkflowParentLifecycleDecision=exports.deriveWorkflowParentLifecycleChange=exports.deriveWorkflowParentLifecycle=exports.createWorkflowParentPersistSink=exports.normalizeRunStatus=exports.WORKFLOW_RUN_LEGACY_RECOVERY_GRACE_MS=exports.WORKFLOW_RUN_INTERRUPTED_ERROR=exports.RunManager=exports.WorkflowEngine=void 0;const E=require("./list-view");const m=b(require("fs"));const h=b(require("path"));const A=b(require("js-yaml"));const v=require("./mermaid");const R=require("./utils/shell-security");const k=require("./run-manager");const O=require("./retry-policy");const S={agent_task:["agent"],skill_task:["skill"],shell_task:["command"],parallel:["branches","join"],join:[],branch:["condition","branches"],approval:["description","on_approve","on_reject"],expression:["set","next"],end:[],error:[]};function w(a,e,i){if(i>10)return null;const r=a.replace("#/","").split("/");let n=e;for(const s of r){n=n?.[s];if(!n)return null}const t=n;if(t.$ref)return w(t.$ref,e,i+1);return t}function L(a){if(a===null||a===void 0)return"null";if(Array.isArray(a))return"array";return typeof a}function y(a,e,i,r,n,t){if(t>20||!e)return;if(e.$ref){const s=w(e.$ref,i,0);if(!s){n.push({path:r,message:`cannot resolve $ref "${e.$ref}"`});return}e=s}if(e.type){const s=L(a);const f=e.type==="integer"?s==="number"&&Number.isInteger(a):e.type===s;if(!f){n.push({path:r,message:`expected type "${e.type}", got "${s}"`});return}}if(typeof a==="number"){if(e.minimum!==void 0&&a<e.minimum){n.push({path:r,message:`number must be >= ${e.minimum}`})}if(e.maximum!==void 0&&a>e.maximum){n.push({path:r,message:`number must be <= ${e.maximum}`})}}if(e.enum){if(!e.enum.includes(a)){n.push({path:r,message:`value "${a}" not in enum [${e.enum.join(", ")}]`})}}if(e.type==="object"&&typeof a==="object"&&a!==null&&!Array.isArray(a)){const s=a;if(e.required){for(const f of e.required){if(!(f in s)){n.push({path:`${r}.${f}`,message:"required field missing"})}}}if(e.properties){for(const[f,o]of Object.entries(e.properties)){if(f in s){y(s[f],o,i,`${r}.${f}`,n,t+1)}}}if(e.additionalProperties&&typeof e.additionalProperties==="object"){const f=e.properties?Object.keys(e.properties):[];for(const o of Object.keys(s)){if(!f.includes(o)){y(s[o],e.additionalProperties,i,`${r}.${o}`,n,t+1)}}}}if(e.type==="array"&&Array.isArray(a)){if(e.items){a.forEach((s,f)=>{y(s,e.items,i,`${r}[${f}]`,n,t+1)})}}}function T(a,e,i,r){const n=S[e.type];if(!n)return;for(const t of n){if(!(t in e)){r.push({path:`${i}.${t}`,message:`"${e.type}" node requires "${t}" field`})}}if(e.type==="parallel"&&e.branches&&!Array.isArray(e.branches)){r.push({path:`${i}.branches`,message:'"parallel" node branches must be an array of node IDs'})}if(e.type==="branch"&&e.branches&&Array.isArray(e.branches)){r.push({path:`${i}.branches`,message:'"branch" node branches must be a Record (object), not an array'})}if(e.type==="branch"&&typeof e.condition==="string"){try{(0,k.validateSafeBranchCondition)(e.condition)}catch(t){r.push({path:`${i}.condition`,message:`unsafe branch condition: ${t.message}`})}}if(typeof e.fail_when==="string"){const t=["agent_task","skill_task","shell_task"];if(!t.includes(e.type)){r.push({path:`${i}.fail_when`,message:`"fail_when" is only supported on task nodes (${t.join(", ")}), not "${e.type}"`})}else{try{(0,k.validateSafeBranchCondition)(e.fail_when)}catch(s){r.push({path:`${i}.fail_when`,message:`unsafe fail_when condition: ${s.message}`})}}}if(e.retry!==void 0){if(!O.RETRYABLE_NODE_TYPES.includes(e.type)){r.push({path:`${i}.retry`,message:'"retry" is only supported on agent_task, skill_task, and shell_task nodes'})}else{const t=(0,O.parseRetryPolicy)(e);if(!t.ok){const s=`${i}.${t.path}`;if(!r.some(f=>f.path===s)){r.push({path:s,message:t.reason})}}}}if(e.type==="skill_task"&&e.skill!==void 0&&!R.SKILL_NAME_RE.test(e.skill)){r.push({path:`${i}.skill`,message:`skill name "${e.skill}" is invalid (must match ^[a-z0-9][a-z0-9-]*$, no path traversal)`})}if(e.type==="shell_task"&&e.command!==void 0){const t=(0,R.validateShellCommand)(e.command);if(!t.ok){r.push({path:`${i}.command`,message:t.error})}const s=(0,R.validateEnvKeys)(e.env);if(!s.ok){r.push({path:`${i}.env`,message:s.error})}}}function K(a,e){const i=a.length;const r=e.length;const n=Array.from({length:i+1},()=>Array(r+1).fill(0));for(let t=0;t<=i;t++)n[t][0]=t;for(let t=0;t<=r;t++)n[0][t]=t;for(let t=1;t<=i;t++){for(let s=1;s<=r;s++){n[t][s]=a[t-1]===e[s-1]?n[t-1][s-1]:1+Math.min(n[t-1][s],n[t][s-1],n[t-1][s-1])}}return n[i][r]}function F(a,e){let i=null;let r=Infinity;for(const n of e){const t=K(a,n);if(t<r&&t<=3){r=t;i=n}}return i}function q(a,e,i,r){const n=e.nodes;const t=new Set(Object.keys(n));const s=`workflows.${a}`;for(const[f,o]of Object.entries(n)){const c=`${s}.nodes.${f}`;if(o.next&&!t.has(o.next)){r.push({path:`${c}.next`,message:`"${o.next}" node not found`})}if(o.agent&&i&&!i[o.agent]){r.push({path:`${c}.agent`,message:`agent "${o.agent}" not found in agents section`})}if(o.type==="parallel"&&Array.isArray(o.branches)){o.branches.forEach((p,l)=>{if(!t.has(p)){r.push({path:`${c}.branches[${l}]`,message:`"${p}" node not found`})}});if(o.join&&!t.has(o.join)){r.push({path:`${c}.join`,message:`"${o.join}" node not found`})}}if(o.type==="branch"&&o.branches&&typeof o.branches==="object"&&!Array.isArray(o.branches)){for(const[p,l]of Object.entries(o.branches)){if(!t.has(l)){const _=F(l,t);let g=`"${l}" node not found`;if(_)g+=` (did you mean "${_}"?)`;r.push({path:`${c}.branches.${p}`,message:g})}}}if(o.default&&!t.has(o.default)){r.push({path:`${c}.default`,message:`"${o.default}" node not found`})}if(o.type==="approval"){if(o.on_approve&&!t.has(o.on_approve)){r.push({path:`${c}.on_approve`,message:`"${o.on_approve}" node not found`})}if(o.on_reject&&!t.has(o.on_reject)){r.push({path:`${c}.on_reject`,message:`"${o.on_reject}" node not found`})}}if(o.on_error?.fallback_node&&!t.has(o.on_error.fallback_node)){r.push({path:`${c}.on_error.fallback_node`,message:`"${o.on_error.fallback_node}" node not found`})}}}class M{constructor(){let e=h.join(__dirname,"..","workflow-schema.json");if(!m.existsSync(e)){e=h.join(__dirname,"..","..","workflow-schema.json")}this.schema=JSON.parse(m.readFileSync(e,"utf-8"))}loadWorkflows(e){return(0,E.loadWorkflowDirectory)(e)}validateFile(e){let i;try{i=m.readFileSync(e,"utf-8")}catch{return{success:false,results:[],error:`File not found or unreadable: ${e}`}}let r;try{r=A.load(i)}catch(f){const o=f instanceof Error?f.message:String(f);return{success:false,results:[],error:`YAML parse error: ${o}`}}if(!r||typeof r!=="object"){return{success:false,results:[],error:"Invalid YAML: document is empty or not an object"}}if(!r.workflows||typeof r.workflows!=="object"){return{success:true,results:[]}}const n=r.agents??null;const t=[];let s=false;for(const[f,o]of Object.entries(r.workflows)){const c=[];const p=`workflows.${f}`;y(o,this.schema.definitions["WorkflowSpec"],this.schema,p,c,0);if(o.nodes&&typeof o.nodes==="object"){for(const[l,_]of Object.entries(o.nodes)){if(_&&typeof _==="object"&&_.type){T(l,_,`${p}.nodes.${l}`,c)}}}q(f,o,n,c);if(c.length>0)s=true;t.push({name:f,valid:c.length===0,errors:c})}return{success:!s,results:t}}toMermaid(e){return(0,v.toMermaid)(e)}}exports.WorkflowEngine=M;var d=require("./run-manager");Object.defineProperty(exports,"RunManager",{enumerable:true,get:function(){return d.RunManager}});Object.defineProperty(exports,"WORKFLOW_RUN_INTERRUPTED_ERROR",{enumerable:true,get:function(){return d.WORKFLOW_RUN_INTERRUPTED_ERROR}});Object.defineProperty(exports,"WORKFLOW_RUN_LEGACY_RECOVERY_GRACE_MS",{enumerable:true,get:function(){return d.WORKFLOW_RUN_LEGACY_RECOVERY_GRACE_MS}});Object.defineProperty(exports,"normalizeRunStatus",{enumerable:true,get:function(){return d.normalizeRunStatus}});var u=require("./parent-task-sync");Object.defineProperty(exports,"createWorkflowParentPersistSink",{enumerable:true,get:function(){return u.createWorkflowParentPersistSink}});Object.defineProperty(exports,"deriveWorkflowParentLifecycle",{enumerable:true,get:function(){return u.deriveWorkflowParentLifecycle}});Object.defineProperty(exports,"deriveWorkflowParentLifecycleChange",{enumerable:true,get:function(){return u.deriveWorkflowParentLifecycleChange}});Object.defineProperty(exports,"deriveWorkflowParentLifecycleDecision",{enumerable:true,get:function(){return u.deriveWorkflowParentLifecycleDecision}});Object.defineProperty(exports,"previewWorkflowParentRepair",{enumerable:true,get:function(){return u.previewWorkflowParentRepair}});Object.defineProperty(exports,"reconcileWorkflowParentRunFiles",{enumerable:true,get:function(){return u.reconcileWorkflowParentRunFiles}});Object.defineProperty(exports,"reconcileWorkflowParentTasks",{enumerable:true,get:function(){return u.reconcileWorkflowParentTasks}});Object.defineProperty(exports,"syncWorkflowParentTaskLifecycle",{enumerable:true,get:function(){return u.syncWorkflowParentTaskLifecycle}});Object.defineProperty(exports,"WORKFLOW_PARENT_ORPHAN_ERROR",{enumerable:true,get:function(){return u.WORKFLOW_PARENT_ORPHAN_ERROR}});Object.defineProperty(exports,"WORKFLOW_PARENT_ORPHAN_GRACE_MS",{enumerable:true,get:function(){return u.WORKFLOW_PARENT_ORPHAN_GRACE_MS}});Object.defineProperty(exports,"WORKFLOW_PARENT_TASK_KINDS",{enumerable:true,get:function(){return u.WORKFLOW_PARENT_TASK_KINDS}});var P=require("./workflow-persistence");Object.defineProperty(exports,"createWorkflowPersistenceProjection",{enumerable:true,get:function(){return P.createWorkflowPersistenceProjection}});Object.defineProperty(exports,"previewWorkflowPersistenceProjection",{enumerable:true,get:function(){return P.previewWorkflowPersistenceProjection}});var W=require("./run-projection");Object.defineProperty(exports,"projectRunSteps",{enumerable:true,get:function(){return W.projectRunSteps}});Object.defineProperty(exports,"summarizeRunProgress",{enumerable:true,get:function(){return W.summarizeRunProgress}});var C=require("./run-anchor");Object.defineProperty(exports,"createWorkflowRunAnchor",{enumerable:true,get:function(){return C.createWorkflowRunAnchor}});var D=require("./card-state");Object.defineProperty(exports,"buildWorkflowApprovalState",{enumerable:true,get:function(){return D.buildWorkflowApprovalState}});
@@ -1,6 +1,7 @@
1
+ import { type TaskTerminalReason, type WorkflowAnchorTaskKind } from '@crewx/sdk';
1
2
  import type { RunExecution } from './types.js';
2
- export type WorkflowParentTaskStatus = 'pending' | 'running' | 'success' | 'failed';
3
- export type WorkflowParentTaskKind = 'workflow_card' | 'workflow_ui_instance';
3
+ export type WorkflowParentTaskStatus = 'pending' | 'running' | 'success' | 'failed' | 'stopped';
4
+ export type WorkflowParentTaskKind = WorkflowAnchorTaskKind;
4
5
  export interface WorkflowParentTaskLookup {
5
6
  workspaceId: string;
6
7
  workflowRunId: string;
@@ -11,6 +12,7 @@ export interface WorkflowParentLifecycle {
11
12
  error: string | null;
12
13
  completedAt: string | null;
13
14
  durationMs: number | null;
15
+ terminalReason?: TaskTerminalReason;
14
16
  }
15
17
  export interface WorkflowParentLifecycleSnapshot {
16
18
  status: string;
@@ -48,6 +50,7 @@ export interface WorkflowParentTaskRepository {
48
50
  completedAt: string | null;
49
51
  durationMs: number | null;
50
52
  workspaceId: string;
53
+ terminalReason?: TaskTerminalReason;
51
54
  }): void;
52
55
  }
53
56
  export interface WorkflowParentPersistTaskRepository {
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.WORKFLOW_PARENT_TASK_KINDS=exports.WORKFLOW_PARENT_ORPHAN_ERROR=exports.WORKFLOW_PARENT_ORPHAN_GRACE_MS=void 0;exports.deriveWorkflowParentLifecycle=p;exports.deriveWorkflowParentLifecycleDecision=k;exports.deriveWorkflowParentLifecycleChange=T;exports.syncWorkflowParentTaskLifecycle=K;exports.previewWorkflowParentRepair=M;exports.createWorkflowParentPersistSink=L;exports.reconcileWorkflowParentTasks=E;exports.reconcileWorkflowParentRunFiles=v;exports.WORKFLOW_PARENT_ORPHAN_GRACE_MS=10*60*1e3;exports.WORKFLOW_PARENT_ORPHAN_ERROR="Workflow run file missing after grace period (orphaned workflow task)";exports.WORKFLOW_PARENT_TASK_KINDS=["workflow_card","workflow_ui_instance"];function w(e){let n;for(const t of e.audit??[]){const r=Date.parse(t.ended_at);if(!Number.isFinite(r))continue;if(!n||r>=n.timestamp){n={value:t.ended_at,timestamp:r}}}return n?.value??null}function p(e){if(e.status==="completed"||e.status==="failed"||e.status==="cancelled"){const n=w(e)??e.updated_at;const t=Date.parse(e.created_at);const r=Date.parse(n);const a=Number.isFinite(t)&&Number.isFinite(r)?Math.max(0,r-t):null;if(e.status==="completed"){return{status:"success",error:null,completedAt:n,durationMs:a}}return{status:"failed",error:e.error??(e.status==="cancelled"?"Workflow run cancelled":"Workflow run failed"),completedAt:n,durationMs:a}}if(e.status==="running"||e.status==="retrying"){return{status:"running",error:null,completedAt:null,durationMs:null}}if(e.status==="pending"||e.status===void 0){return{status:"pending",error:null,completedAt:null,durationMs:null}}return null}function _(e){if(!e)return null;try{const n=JSON.parse(e);if(!n||typeof n!=="object"||Array.isArray(n))return null;const t=n;return(t.kind==="workflow_card"||t.kind==="workflow_ui_instance")&&typeof t.workflowRunId==="string"?{kind:t.kind,workflowRunId:t.workflowRunId,...typeof t.uiInstanceId==="string"&&t.uiInstanceId.trim()?{uiInstanceId:t.uiInstanceId.trim()}:{}}:null}catch{return null}}function h(e,n){return e.status===n.status&&e.error===n.error&&e.completed_at===n.completedAt&&e.duration_ms===n.durationMs}function O(e){return e==="success"||e==="completed"||e==="failed"||e==="cancelled"||e==="error"}function N(e,n,t){if(!O(e.status))return false;if(!e.completed_at)return false;const r=Date.parse(e.completed_at);const a=Date.parse(t??n.updated_at);if(!Number.isFinite(r)||!Number.isFinite(a))return true;return r>=a}function k(e){const n=()=>({status:"ineligible",change:null,linkedAuditInstanceId:null});if(e.row.platform!=="workflow"||e.row.workspace_id!==e.workspaceId){return n()}const t=_(e.row.metadata);if(!t||t.kind!==e.kind||t.workflowRunId!==e.run.id){return n()}const r=p(e.run);if(!r)return n();const a=t.kind==="workflow_ui_instance"?t.uiInstanceId??null:null;if(h(e.row,r)){return{status:"unchanged",change:null,linkedAuditInstanceId:a}}if((r.status==="running"||r.status==="pending")&&N(e.row,e.run,e.snapshotUpdatedAt)){return{status:"stale-suppressed",change:null,linkedAuditInstanceId:null}}const i=e.run.status??"pending";const o=i==="pending"?"Persisted run is pending (or legacy statusless); no execution outcome is inferred.":`Persisted run status "${i}" is projected to the parent lifecycle.`;return{status:"changed",linkedAuditInstanceId:a,change:{id:e.row.id,kind:e.kind,before:{status:e.row.status,error:e.row.error,completedAt:e.row.completed_at,durationMs:e.row.duration_ms},after:r,reason:o}}}function T(e){return k(e).change}function R(e,n){e.taskRepo.updateTaskLifecycle({id:e.row.id,status:n.after.status,error:n.after.error,completedAt:n.after.completedAt,durationMs:n.after.durationMs,workspaceId:e.workspaceId})}function K(e){const n=k(e).change;if(!n)return false;R(e,n);return true}function A(e){const n=[];for(const t of e.parentKinds){const r=e.taskRepo.findWorkflowParentTask({workspaceId:e.workspaceId,workflowRunId:e.run.id,kind:t});if(!r)continue;n.push({kind:t,row:r,decision:k({row:r,run:e.run,workspaceId:e.workspaceId,kind:t,snapshotUpdatedAt:w(e.run)})})}return n}function S(e,n){const t=new Map;for(const r of n){if(r.platform!=="workflow"||r.workspace_id!==e)continue;const a=_(r.metadata);if(!a)continue;const i=t.get(a.kind)??new Map;if(!i.has(a.workflowRunId)){i.set(a.workflowRunId,r)}t.set(a.kind,i)}return{findWorkflowParentTask:r=>{if(r.workspaceId!==e)return void 0;return t.get(r.kind)?.get(r.workflowRunId)}}}function M(e){const n=e.parentKinds??exports.WORKFLOW_PARENT_TASK_KINDS;const t=[];const r=new Set;for(const{decision:a}of A({taskRepo:e.taskRepo,run:e.run,workspaceId:e.workspaceId,parentKinds:n})){if(a.linkedAuditInstanceId){r.add(a.linkedAuditInstanceId)}if(a.change)t.push(a.change)}return{runId:e.run.id,runStatus:e.run.status??"pending",changes:t,linkedAuditInstanceIds:[...r]}}function L(e){const n=e.parentKinds??exports.WORKFLOW_PARENT_TASK_KINDS;return t=>{const r=[];const a=new Set;if(!p(t)){return{changedParentTaskIds:r,linkedAuditInstanceIds:[]}}for(const{kind:i,row:o,decision:s}of A({taskRepo:e.taskRepo,run:t,workspaceId:e.workspaceId,parentKinds:n})){if(s.linkedAuditInstanceId){a.add(s.linkedAuditInstanceId)}if(s.change){R({taskRepo:e.taskRepo,row:o,run:t,workspaceId:e.workspaceId,kind:i,snapshotUpdatedAt:w(t)},s.change);r.push(o.id)}}return{changedParentTaskIds:r,linkedAuditInstanceIds:[...a]}}}function F(e,n,t){const r=Date.parse(e);return Number.isFinite(r)&&n-r>=t}function m(){return{}}function u(e,n){e.skipped+=1;const t=e.skippedBy??(e.skippedBy={});t[n]=(t[n]??0)+1}function E(e){const n=e.orphanGraceMs??exports.WORKFLOW_PARENT_ORPHAN_GRACE_MS;const t=e.nowMs??Date.now();const r={aligned:0,orphaned:0,scanned:0,skipped:0,skippedBy:m()};const a=s=>{u(r,s)};const i=new Map;const o=s=>{if(!i.has(s)){i.set(s,e.resolveWorkspacePath(s))}return i.get(s)};for(const s of e.taskRepo.findRunningWorkflowParentTasks()){r.scanned=(r.scanned??0)+1;const l=_(s.metadata);if(s.platform!=="workflow"||!l){a("no-marker");continue}const c=s.workspace_id;if(!c){a("no-workspace-id");continue}const d=o(c);if(!d){a("no-workspace-path");continue}const W=l.workflowRunId;let f=null;try{f=e.loadRun(d,W)}catch{}if(!f){if(s.status!=="running"||!F(s.started_at,t,n)){a(s.status==="running"?"not-stale":"run-missing");continue}const y=s.completed_at??new Date(t).toISOString();const g=s.duration_ms??Math.max(0,t-Date.parse(s.started_at));e.taskRepo.updateTaskLifecycle({id:s.id,status:"failed",error:exports.WORKFLOW_PARENT_ORPHAN_ERROR,completedAt:y,durationMs:Number.isFinite(g)?g:null,workspaceId:c});r.orphaned+=1;continue}const P=p(f);if(!P){a("not-stale");continue}if(h(s,P))continue;const I=k({row:s,run:f,workspaceId:c,kind:l.kind});if(I.status==="stale-suppressed"){a("not-stale");continue}if(I.change){R({taskRepo:e.taskRepo,row:s,run:f,workspaceId:c,kind:l.kind},I.change);r.aligned+=1}}return r}function v(e){const n={aligned:0,orphaned:0,skipped:0,skippedBy:m(),runFilesScanned:0};for(const t of e.workspaces){if(!t.workspaceId){u(n,"no-workspace-id");continue}if(!t.workspacePath){u(n,"no-workspace-path");continue}const r=e.listWorkflowParentTasks?S(t.workspaceId,e.listWorkflowParentTasks(t.workspaceId)):e.taskRepo;let a=[];try{a=e.listRunIds(t.workspacePath)}catch{u(n,"no-workspace-path");continue}for(const i of a){n.runFilesScanned=(n.runFilesScanned??0)+1;let o=null;try{o=e.readRun(t.workspacePath,i)}catch{}if(!o||typeof o.id!=="string"){u(n,"run-missing");continue}const s=e.parentKinds??exports.WORKFLOW_PARENT_TASK_KINDS;for(const{kind:l,row:c,decision:d}of A({taskRepo:r,run:o,workspaceId:t.workspaceId,parentKinds:s})){if(d.status==="stale-suppressed"){u(n,"suppressed");continue}if(d.status==="ineligible"){u(n,"ineligible");continue}if(!d.change)continue;R({taskRepo:e.taskRepo,row:c,run:o,workspaceId:t.workspaceId,kind:l,snapshotUpdatedAt:w(o)},d.change);n.aligned+=1}}}return n}
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.WORKFLOW_PARENT_TASK_KINDS=exports.WORKFLOW_PARENT_ORPHAN_ERROR=exports.WORKFLOW_PARENT_ORPHAN_GRACE_MS=void 0;exports.deriveWorkflowParentLifecycle=w;exports.deriveWorkflowParentLifecycleDecision=k;exports.deriveWorkflowParentLifecycleChange=S;exports.syncWorkflowParentTaskLifecycle=L;exports.previewWorkflowParentRepair=F;exports.createWorkflowParentPersistSink=E;exports.reconcileWorkflowParentTasks=v;exports.reconcileWorkflowParentRunFiles=b;const _=require("@crewx/sdk");const N=require("./cancel-reason.js");exports.WORKFLOW_PARENT_ORPHAN_GRACE_MS=10*60*1e3;exports.WORKFLOW_PARENT_ORPHAN_ERROR="Workflow run file missing after grace period (orphaned workflow task)";exports.WORKFLOW_PARENT_TASK_KINDS=_.WORKFLOW_ANCHOR_TASK_KINDS;function p(e){let n;for(const t of e.audit??[]){const r=Date.parse(t.ended_at);if(!Number.isFinite(r))continue;if(!n||r>=n.timestamp){n={value:t.ended_at,timestamp:r}}}return n?.value??null}function w(e){if(e.status==="completed"||e.status==="failed"||e.status==="cancelled"){const n=p(e)??e.updated_at;const t=Date.parse(e.created_at);const r=Date.parse(n);const a=Number.isFinite(t)&&Number.isFinite(r)?Math.max(0,r-t):null;if(e.status==="completed"){return{status:"success",error:null,completedAt:n,durationMs:a}}if(e.status==="cancelled"&&(0,N.isUserCancelledRun)(e)){return{status:"stopped",error:_.CANONICAL_USER_STOP_ERROR,completedAt:n,durationMs:a,terminalReason:"user_stop"}}return{status:"failed",error:e.error??"Workflow run failed",completedAt:n,durationMs:a}}if(e.status==="running"||e.status==="retrying"){return{status:"running",error:null,completedAt:null,durationMs:null}}if(e.status==="pending"||e.status===void 0){return{status:"pending",error:null,completedAt:null,durationMs:null}}return null}function A(e){if(!e)return null;try{const n=JSON.parse(e);if(!n||typeof n!=="object"||Array.isArray(n))return null;const t=n;return(0,_.isWorkflowAnchorTaskKind)(t.kind)&&typeof t.workflowRunId==="string"?{kind:t.kind,workflowRunId:t.workflowRunId,...typeof t.uiInstanceId==="string"&&t.uiInstanceId.trim()?{uiInstanceId:t.uiInstanceId.trim()}:{}}:null}catch{return null}}function g(e,n){return e.status===n.status&&e.error===n.error&&e.completed_at===n.completedAt&&e.duration_ms===n.durationMs}function T(e){return e==="success"||e==="completed"||e==="failed"||e==="stopped"||e==="cancelled"||e==="error"}function K(e,n,t){if(!T(e.status))return false;if(!e.completed_at)return false;const r=Date.parse(e.completed_at);const a=Date.parse(t??n.updated_at);if(!Number.isFinite(r)||!Number.isFinite(a))return true;return r>=a}function k(e){const n=()=>({status:"ineligible",change:null,linkedAuditInstanceId:null});if(e.row.platform!=="workflow"||e.row.workspace_id!==e.workspaceId){return n()}const t=A(e.row.metadata);if(!t||t.kind!==e.kind||t.workflowRunId!==e.run.id){return n()}const r=w(e.run);if(!r)return n();const a=t.kind==="workflow_ui_instance"?t.uiInstanceId??null:null;if(g(e.row,r)){return{status:"unchanged",change:null,linkedAuditInstanceId:a}}if((r.status==="running"||r.status==="pending")&&K(e.row,e.run,e.snapshotUpdatedAt)){return{status:"stale-suppressed",change:null,linkedAuditInstanceId:null}}const i=e.run.status??"pending";const o=i==="pending"?"Persisted run is pending (or legacy statusless); no execution outcome is inferred.":`Persisted run status "${i}" is projected to the parent lifecycle.`;return{status:"changed",linkedAuditInstanceId:a,change:{id:e.row.id,kind:e.kind,before:{status:e.row.status,error:e.row.error,completedAt:e.row.completed_at,durationMs:e.row.duration_ms},after:r,reason:o}}}function S(e){return k(e).change}function R(e,n){e.taskRepo.updateTaskLifecycle({id:e.row.id,status:n.after.status,error:n.after.error,completedAt:n.after.completedAt,durationMs:n.after.durationMs,workspaceId:e.workspaceId,...n.after.terminalReason?{terminalReason:n.after.terminalReason}:{}})}function L(e){const n=k(e).change;if(!n)return false;R(e,n);return true}function P(e){const n=[];for(const t of e.parentKinds){const r=e.taskRepo.findWorkflowParentTask({workspaceId:e.workspaceId,workflowRunId:e.run.id,kind:t});if(!r)continue;n.push({kind:t,row:r,decision:k({row:r,run:e.run,workspaceId:e.workspaceId,kind:t,snapshotUpdatedAt:p(e.run)})})}return n}function M(e,n){const t=new Map;for(const r of n){if(r.platform!=="workflow"||r.workspace_id!==e)continue;const a=A(r.metadata);if(!a)continue;const i=t.get(a.kind)??new Map;if(!i.has(a.workflowRunId)){i.set(a.workflowRunId,r)}t.set(a.kind,i)}return{findWorkflowParentTask:r=>{if(r.workspaceId!==e)return void 0;return t.get(r.kind)?.get(r.workflowRunId)}}}function F(e){const n=e.parentKinds??exports.WORKFLOW_PARENT_TASK_KINDS;const t=[];const r=new Set;for(const{decision:a}of P({taskRepo:e.taskRepo,run:e.run,workspaceId:e.workspaceId,parentKinds:n})){if(a.linkedAuditInstanceId){r.add(a.linkedAuditInstanceId)}if(a.change)t.push(a.change)}return{runId:e.run.id,runStatus:e.run.status??"pending",changes:t,linkedAuditInstanceIds:[...r]}}function E(e){const n=e.parentKinds??exports.WORKFLOW_PARENT_TASK_KINDS;return t=>{const r=[];const a=new Set;if(!w(t)){return{changedParentTaskIds:r,linkedAuditInstanceIds:[]}}for(const{kind:i,row:o,decision:s}of P({taskRepo:e.taskRepo,run:t,workspaceId:e.workspaceId,parentKinds:n})){if(s.linkedAuditInstanceId){a.add(s.linkedAuditInstanceId)}if(s.change){R({taskRepo:e.taskRepo,row:o,run:t,workspaceId:e.workspaceId,kind:i,snapshotUpdatedAt:p(t)},s.change);r.push(o.id)}}return{changedParentTaskIds:r,linkedAuditInstanceIds:[...a]}}}function D(e,n,t){const r=Date.parse(e);return Number.isFinite(r)&&n-r>=t}function h(){return{}}function u(e,n){e.skipped+=1;const t=e.skippedBy??(e.skippedBy={});t[n]=(t[n]??0)+1}function v(e){const n=e.orphanGraceMs??exports.WORKFLOW_PARENT_ORPHAN_GRACE_MS;const t=e.nowMs??Date.now();const r={aligned:0,orphaned:0,scanned:0,skipped:0,skippedBy:h()};const a=s=>{u(r,s)};const i=new Map;const o=s=>{if(!i.has(s)){i.set(s,e.resolveWorkspacePath(s))}return i.get(s)};for(const s of e.taskRepo.findRunningWorkflowParentTasks()){r.scanned=(r.scanned??0)+1;const l=A(s.metadata);if(s.platform!=="workflow"||!l){a("no-marker");continue}const c=s.workspace_id;if(!c){a("no-workspace-id");continue}const d=o(c);if(!d){a("no-workspace-path");continue}const O=l.workflowRunId;let f=null;try{f=e.loadRun(d,O)}catch{}if(!f){if(s.status!=="running"||!D(s.started_at,t,n)){a(s.status==="running"?"not-stale":"run-missing");continue}const y=s.completed_at??new Date(t).toISOString();const W=s.duration_ms??Math.max(0,t-Date.parse(s.started_at));e.taskRepo.updateTaskLifecycle({id:s.id,status:"failed",error:exports.WORKFLOW_PARENT_ORPHAN_ERROR,completedAt:y,durationMs:Number.isFinite(W)?W:null,workspaceId:c});r.orphaned+=1;continue}const m=w(f);if(!m){a("not-stale");continue}if(g(s,m))continue;const I=k({row:s,run:f,workspaceId:c,kind:l.kind});if(I.status==="stale-suppressed"){a("not-stale");continue}if(I.change){R({taskRepo:e.taskRepo,row:s,run:f,workspaceId:c,kind:l.kind},I.change);r.aligned+=1}}return r}function b(e){const n={aligned:0,orphaned:0,skipped:0,skippedBy:h(),runFilesScanned:0};for(const t of e.workspaces){if(!t.workspaceId){u(n,"no-workspace-id");continue}if(!t.workspacePath){u(n,"no-workspace-path");continue}const r=e.listWorkflowParentTasks?M(t.workspaceId,e.listWorkflowParentTasks(t.workspaceId)):e.taskRepo;let a=[];try{a=e.listRunIds(t.workspacePath)}catch{u(n,"no-workspace-path");continue}for(const i of a){n.runFilesScanned=(n.runFilesScanned??0)+1;let o=null;try{o=e.readRun(t.workspacePath,i)}catch{}if(!o||typeof o.id!=="string"){u(n,"run-missing");continue}const s=e.parentKinds??exports.WORKFLOW_PARENT_TASK_KINDS;for(const{kind:l,row:c,decision:d}of P({taskRepo:r,run:o,workspaceId:t.workspaceId,parentKinds:s})){if(d.status==="stale-suppressed"){u(n,"suppressed");continue}if(d.status==="ineligible"){u(n,"ineligible");continue}if(!d.change)continue;R({taskRepo:e.taskRepo,row:c,run:o,workspaceId:t.workspaceId,kind:l,snapshotUpdatedAt:p(o)},d.change);n.aligned+=1}}}return n}
@@ -0,0 +1,9 @@
1
+ import type { TaskRepository } from '@crewx/sdk/repository';
2
+ import type { RunExecution } from './types.js';
3
+ export interface CreateWorkflowRunAnchorOptions {
4
+ taskRepo: Pick<TaskRepository, 'startTask' | 'findWorkflowParentTask'>;
5
+ run: RunExecution;
6
+ workspaceId: string;
7
+ }
8
+ export declare function createWorkflowRunAnchor(options: CreateWorkflowRunAnchorOptions): string;
9
+
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.createWorkflowRunAnchor=a;const s=require("@crewx/sdk");function a(i){const{taskRepo:o,run:t,workspaceId:r}=i;const n=o.findWorkflowParentTask({workspaceId:r,workflowRunId:t.id,kind:"workflow_run"});if(n)return n.id;const e=(0,s.generateId)("tsk");const d=typeof t.state["input"]==="string"?t.state["input"].trim():"";o.startTask({id:e,agentId:"workflow",actorKind:"system",actorId:null,prompt:["/wf",t.workflow_id,...d?[d]:[]].join(" "),mode:"workflow",status:"pending",startedAt:t.created_at,pid:null,workspaceId:r,threadId:null,platform:"workflow",metadata:JSON.stringify({kind:"workflow_run",workflowId:t.workflow_id,workflowRunId:t.id})});return e}
@@ -1,5 +1,6 @@
1
1
  import type { RunExecution, WorkflowSpec, AutoRunResult } from './types';
2
2
  export { FileLock } from './file-lock';
3
+ export { USER_CANCEL_REASON } from './cancel-reason';
3
4
  export declare const SKILL_TASK_DEFAULT_TIMEOUT = 600000;
4
5
  export declare const WORKFLOW_CANCEL_GRACE_MS = 2000;
5
6
  export declare const WORKFLOW_RUN_INTERRUPTED_ERROR = "Workflow run interrupted: runner process exited before completion";
@@ -58,6 +59,11 @@ export declare class RunManager {
58
59
  private runNodeWithRetry;
59
60
  runAuto(execId: string): Promise<AutoRunResult>;
60
61
  cancelRun(execId: string, reason?: string): RunExecution;
62
+ decideApproval(execId: string, decision: 'approve' | 'reject', options: {
63
+ spec: WorkflowSpec;
64
+ actor: string;
65
+ reason?: string;
66
+ }): RunExecution;
61
67
  reset(execId: string): RunExecution;
62
68
  retry(execId: string, options?: {
63
69
  autoRun?: boolean;
@@ -1,4 +1,4 @@
1
- "use strict";var xe=exports&&exports.__createBinding||(Object.create?(function(a,e,t,o){if(o===void 0)o=t;var r=Object.getOwnPropertyDescriptor(e,t);if(!r||("get"in r?!e.__esModule:r.writable||r.configurable)){r={enumerable:true,get:function(){return e[t]}}}Object.defineProperty(a,o,r)}):(function(a,e,t,o){if(o===void 0)o=t;a[o]=e[t]}));var Ie=exports&&exports.__setModuleDefault||(Object.create?(function(a,e){Object.defineProperty(a,"default",{enumerable:true,value:e})}):function(a,e){a["default"]=e});var ie=exports&&exports.__importStar||(function(){var a=function(e){a=Object.getOwnPropertyNames||function(t){var o=[];for(var r in t)if(Object.prototype.hasOwnProperty.call(t,r))o[o.length]=r;return o};return a(e)};return function(e){if(e&&e.__esModule)return e;var t={};if(e!=null){for(var o=a(e),r=0;r<o.length;r++)if(o[r]!=="default")xe(t,e,o[r])}Ie(t,e);return t}})();Object.defineProperty(exports,"__esModule",{value:true});exports.RunManager=exports.WORKFLOW_RUN_LEGACY_RECOVERY_GRACE_MS=exports.WORKFLOW_RUN_INTERRUPTED_ERROR=exports.WORKFLOW_CANCEL_GRACE_MS=exports.SKILL_TASK_DEFAULT_TIMEOUT=exports.FileLock=void 0;exports.parseSetArgs=He;exports.readStdin=Ze;exports.interpolateTemplate=b;exports.safeEvaluate=Ee;exports.safeEvaluateBranchCondition=de;exports.validateSafeBranchCondition=st;exports.normalizeRunStatus=ee;exports.isRetryableFailure=Ue;const R=ie(require("fs"));const W=ie(require("path"));const Me=ie(require("js-yaml"));const Ge=ie(require("child_process"));const N=require("@crewx/sdk");const d=require("./failure");const le=require("./file-lock");const K=require("./utils/output-format");const U=require("./utils/shell-security");const ce=require("./retry-policy");var Ke=require("./file-lock");Object.defineProperty(exports,"FileLock",{enumerable:true,get:function(){return Ke.FileLock}});exports.SKILL_TASK_DEFAULT_TIMEOUT=6e5;exports.WORKFLOW_CANCEL_GRACE_MS=2e3;const Ve=100;exports.WORKFLOW_RUN_INTERRUPTED_ERROR="Workflow run interrupted: runner process exited before completion";exports.WORKFLOW_RUN_LEGACY_RECOVERY_GRACE_MS=10*6e4;const qe=1e3;function ze(a){return new Promise(e=>{setTimeout(e,a)})}const Ae=new Set(["agent_task","skill_task","shell_task","branch","expression","join","end","error"]);const Xe=new Set(["branch","expression","end","error"]);function Se(a,e){const t=(0,N.resolveCrewxExecutable)({env:a,cwd:e});if(!t.ok){throw(0,d.createWorkflowFailureError)("CLI_UNRESOLVABLE",(0,N.formatCrewxExecutableFailure)(t))}return(0,N.createCrewxChildInvocation)(t,{baseEnv:a})}async function ye(a,e="SIGKILL"){if(!a.pid)return;try{const t=await(0,N.terminateProcessTree)(a.pid,{signal:e});if(t.status==="failed"||t.status==="not_found"){try{a.kill(e)}catch{}}}catch{try{a.kill(e)}catch{}}}async function ue(a,e,t){return new Promise(o=>{const r=()=>{try{return t.cancellation?.isCancelled()===true}catch{return false}};if(r()){o({status:null,stdout:"",stderr:"",timedOut:false,cancelled:true,signal:null,errorOrigin:null});return}let s;try{s=Ge.spawn(a,e,{cwd:t.cwd,env:t.env,stdio:[t.stdin!==void 0?"pipe":"ignore","pipe","pipe"],shell:t.shell??false,detached:process.platform!=="win32",windowsHide:t.windowsHide??true})}catch(g){o({status:null,stdout:"",stderr:"",timedOut:false,signal:null,errorOrigin:"spawn",error:g});return}let i="";let n="";let c=false;let p=false;let l=false;let f=false;let m;let _;let S;let C;let w;let k;const F=new N.ConsoleDecoder;const H=new N.ConsoleDecoder;let X=Promise.resolve();let q=Promise.resolve();const Z=()=>{if(S)clearTimeout(S);if(C)clearTimeout(C);if(w)clearInterval(w);if(t.cancellation&&k&&fe.get(t.cancellation.key)===k){fe.delete(t.cancellation.key)}};const I=g=>{if(c)return;c=true;Z();o(f?{...g,cancelled:true,timedOut:false}:g)};const B=()=>{if(c||f)return;f=true;void ye(s,"SIGTERM");C=setTimeout(()=>{if(!c)void ye(s,"SIGKILL")},exports.WORKFLOW_CANCEL_GRACE_MS);if(typeof C.unref==="function"){C.unref()}};if(t.cancellation){k={cancel:B};fe.set(t.cancellation.key,k);w=setInterval(()=>{if(r())B()},Ve);if(typeof w.unref==="function")w.unref()}S=setTimeout(()=>{void ye(s);Q(()=>({status:null,stdout:i,stderr:n,timedOut:true,signal:null,errorOrigin:_?"stdin":m?"post_spawn":null,..._||m?{error:_??m}:{}}))},t.timeout);if(typeof S.unref==="function")S.unref();s.stdout?.on("data",g=>{X=X.then(async()=>{i+=await F.write(g)})});s.stderr?.on("data",g=>{q=q.then(async()=>{n+=await H.write(g)})});s.once("spawn",()=>{l=true});s.on("error",g=>{if(!l){I({status:null,stdout:i,stderr:n,timedOut:false,signal:null,errorOrigin:"spawn",error:g});return}m=m??g});if(t.stdin!==void 0){s.stdin?.on("error",g=>{_=_??g});try{s.stdin?.end(t.stdin)}catch(g){_=_??g}}const Q=g=>{if(c||p)return;p=true;void(async()=>{await Promise.all([X,q]);const[M,pe]=await Promise.all([F.end(),H.end()]);i+=M;n+=pe;I(g())})().catch(()=>I(g()))};s.on("close",(g,M)=>Q(()=>({status:g,stdout:i,stderr:n,timedOut:false,signal:M,errorOrigin:_?"stdin":m?"post_spawn":null,..._||m?{error:_??m}:{}})));if(r())B()})}const Be=/^[A-Za-z0-9_.-]{1,128}$/;function be(a){if(a===void 0)return void 0;const e=Be.exec(a);return e?.[0]===a?a:void 0}function Je(a){switch(a.type){case"agent_task":{const e=be(a.agent);return e?`@${e} (${a.mode==="query"?"q":"x"})`:`agent_task (${a.mode==="query"?"q":"x"})`}case"skill_task":{const e=be(a.skill);return e?`skill ${e}`:"skill_task"}case"shell_task":return"shell_task";case"branch":return"branch";case"expression":return"expression";default:return"workflow node"}}function J(a){if(a instanceof Error)return a.message;return typeof a==="string"?a:"Node execution failed"}function Ye(a){return{status:a.status,timedOut:a.timedOut,signal:a.signal,errorOrigin:a.errorOrigin}}function Y(a,e,t,o,r=new Date().toISOString()){a.audit=a.audit??[];a.audit.push({exec_id:a.id,node_id:e.failedNode,type:t,summary:e.causeMessage,started_at:r,ended_at:r,duration_ms:0,exit_code:e.exitCode,error:e.causeMessage,trigger:o})}function V(a,e,t){a.status="failed";a.error=(0,d.sanitizeLocalRunError)(t,e.causeMessage);a.last_failure=e}async function Le(a,e,t,o){if(!a.output)return;const r=t.trim();if(a.output_format==="json"){let s;try{s=JSON.parse((0,K.extractJson)(r));if(a.output_schema){await(0,K.validateJsonSchema)(s,a.output_schema)}}catch(i){throw(0,d.createWorkflowFailureError)("OUTPUT_INVALID",`Node "${e}" output_format=json parse or validation failed: ${J(i)}`)}o[a.output]=s}else{o[a.output]=r}}function We(a){try{return JSON.parse(a)}catch{return a}}function He(a){const e={};for(let t=0;t<a.length;t++){const o=a[t];if(o==="--set"&&t+1<a.length){const r=a[t+1];const s=r.indexOf("=");if(s!==-1){const i=r.substring(0,s);const n=r.substring(s+1);e[i]=We(n)}t++}else if(o.startsWith("--set=")){const r=o.substring("--set=".length);const s=r.indexOf("=");if(s!==-1){const i=r.substring(0,s);const n=r.substring(s+1);e[i]=We(n)}}}return e}async function Ze(){const a=[];for await(const e of process.stdin){a.push(e)}return Buffer.concat(a).toString("utf-8").trimEnd()}function b(a,e){return a.replace(/\{\{([\w.]+)\}\}/g,(t,o)=>{const r=o.split(".");let s=e[r[0]];for(let i=1;i<r.length&&s!=null;i++){s=s[r[i]]}if(s===null||s===void 0)return"";if(typeof s==="object")return JSON.stringify(s,null,2);return String(s)})}const Qe=new Set(["require","import","process","global","globalThis","Function","eval","__proto__","constructor","prototype"]);const et=new Set(["__proto__","constructor","prototype"]);function tt(a){const e=[];let t=0;while(t<a.length){const o=a[t];if(/\s/.test(o)){t++;continue}if(/\d/.test(o)){let s="";while(t<a.length&&/[\d.]/.test(a[t])){s+=a[t];t++}e.push({type:"NUMBER",value:Number(s)});continue}if(o==="'"||o==='"'){const s=o;t++;let i="";while(t<a.length&&a[t]!==s){if(a[t]==="\\"){t++;i+=a[t]??""}else{i+=a[t]}t++}if(t>=a.length)throw new Error("Unterminated string literal");t++;e.push({type:"STRING",value:i});continue}if(/[a-zA-Z_]/.test(o)){let s="";while(t<a.length&&/\w/.test(a[t])){s+=a[t];t++}if(s==="true"||s==="false"){e.push({type:"BOOLEAN",value:s==="true"})}else{if(Qe.has(s)){throw new Error(`Forbidden identifier in expression: "${s}"`)}e.push({type:"IDENT",value:s})}continue}if(t+1<a.length){const s=a.substring(t,t+2);const i={">=":"GTE","<=":"LTE","==":"EQ","!=":"NEQ","&&":"AND","||":"OR"};if(i[s]){e.push({type:i[s],value:s});t+=2;continue}}const r={"+":"PLUS","-":"MINUS","*":"STAR","/":"SLASH","%":"PERCENT",">":"GT","<":"LT","!":"NOT","?":"QUESTION",":":"COLON","(":"LPAREN",")":"RPAREN"};if(r[o]){e.push({type:r[o],value:o});t++;continue}if(o==="."||o==="["||o==="]"){throw new Error(`Forbidden character in expression: "${o}" (property access not allowed)`)}throw new Error(`Unexpected character in expression: "${o}"`)}e.push({type:"EOF",value:""});return e}class rt{constructor(e,t){this.tokens=e;this.state=t;this.pos=0}peek(){return this.tokens[this.pos]}advance(){return this.tokens[this.pos++]}expect(e){const t=this.advance();if(t.type!==e)throw new Error(`Expected ${e}, got ${t.type}`);return t}parse(){const e=this.parseTernary();if(this.peek().type!=="EOF"){throw new Error(`Unexpected token after expression: ${this.peek().type}`)}return e}parseTernary(){const e=this.parseOr();if(this.peek().type==="QUESTION"){this.advance();const t=this.parseTernary();this.expect("COLON");const o=this.parseTernary();return e?t:o}return e}parseOr(){let e=this.parseAnd();while(this.peek().type==="OR"){this.advance();const t=this.parseAnd();e=e||t}return e}parseAnd(){let e=this.parseEquality();while(this.peek().type==="AND"){this.advance();const t=this.parseEquality();e=e&&t}return e}parseEquality(){let e=this.parseComparison();while(this.peek().type==="EQ"||this.peek().type==="NEQ"){const t=this.advance().type;const o=this.parseComparison();e=t==="EQ"?e==o:e!=o}return e}parseComparison(){let e=this.parseAdditive();while(["GT","LT","GTE","LTE"].includes(this.peek().type)){const t=this.advance().type;const o=this.parseAdditive();switch(t){case"GT":e=e>o;break;case"LT":e=e<o;break;case"GTE":e=e>=o;break;case"LTE":e=e<=o;break}}return e}parseAdditive(){let e=this.parseMultiplicative();while(this.peek().type==="PLUS"||this.peek().type==="MINUS"){const t=this.advance().type;const o=this.parseMultiplicative();if(t==="PLUS"){if(typeof e==="string"||typeof o==="string"){e=String(e)+String(o)}else{e=e+o}}else{e=e-o}}return e}parseMultiplicative(){let e=this.parseUnary();while(["STAR","SLASH","PERCENT"].includes(this.peek().type)){const t=this.advance().type;const o=this.parseUnary();switch(t){case"STAR":e=e*o;break;case"SLASH":e=e/o;break;case"PERCENT":e=e%o;break}}return e}parseUnary(){if(this.peek().type==="NOT"){this.advance();return!this.parseUnary()}if(this.peek().type==="MINUS"){this.advance();return-this.parseUnary()}return this.parsePrimary()}parsePrimary(){const e=this.peek();switch(e.type){case"NUMBER":this.advance();return e.value;case"STRING":this.advance();return e.value;case"BOOLEAN":this.advance();return e.value;case"IDENT":{this.advance();const t=e.value;const o=this.state[t];if(o===void 0||o===null)return 0;if(typeof o==="boolean"||typeof o==="number")return o;const r=String(o);if(r==="")return"";const s=Number(r);return isNaN(s)?r:s}case"LPAREN":{this.advance();const t=this.parseTernary();this.expect("RPAREN");return t}default:throw new Error(`Unexpected token: ${e.type}`)}}}function Ee(a,e){const t=tt(a);const o=new rt(t,e);return o.parse()}function Fe(a,e){const t=e.split(".");let o=a;for(const r of t){if(et.has(r)){throw new Error(`Forbidden state path segment in expression: "${r}"`)}if(!r||o===null||o===void 0||typeof o!=="object"){return void 0}o=o[r]}return o}function ot(a){return a.replace(/\\'/g,"'").replace(/\\"/g,'"').replace(/\\\\/g,"\\")}function nt(a,e){const t={};let o=0;const r=n=>{const c=`__state_${o++}`;t[c]=n;return c};let s=a.replace(/!==/g,"!=").replace(/===/g,"==");const i=n=>{s=s.replace(n,(c,p,l)=>{const f=Fe(e,p);return r(String(f??"").includes(ot(l)))})};i(/\bstate\.([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)\.includes\(\s*'((?:\\.|[^'\\])*)'\s*\)/g);i(/\bstate\.([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)\.includes\(\s*"((?:\\.|[^"\\])*)"\s*\)/g);s=s.replace(/\bstate\.([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)/g,(n,c)=>r(Fe(e,c)));s=s.replace(/\bNumber\(\s*([A-Za-z_]\w*)\s*\)/g,"$1");s=s.replace(/\bparseInt\(\s*([A-Za-z_]\w*(?:\s*\|\|\s*(?:'[^']*'|"[^"]*"))?)\s*\)/g,"($1)");return{expr:s,scope:t}}function de(a,e){const t=nt(a,e);return Ee(t.expr,{...e,...t.scope})}function st(a){de(a,{})}function at(a){const e=a.state["__engine"];if(!e||typeof e!=="object")return{visitCounts:{},iterations:0};const t=e;const o=t["visitCounts"];const r={};if(o&&typeof o==="object"){for(const[i,n]of Object.entries(o)){if(typeof n==="number"&&Number.isFinite(n))r[i]=n}}const s=typeof t["iterations"]==="number"&&Number.isFinite(t["iterations"])?t["iterations"]:0;return{visitCounts:r,iterations:s}}const ke=new Set;const fe=new Map;function Te(a,e){return`${a}::${e}`}function it(a,e){fe.get(Te(a,e))?.cancel()}function De(a){if(!Number.isInteger(a)||a<=0)return false;if(a===process.pid)return true;try{process.kill(a,0);return true}catch(e){return e.code==="EPERM"}}function Re(a){return a==="completed"||a==="failed"||a==="cancelled"}function lt(a,e){if(a.status!==void 0||a.auto_run)return false;const t=a.audit?.at(-1);if(t?.type!=="retry")return false;const o=Date.parse(a.updated_at);return Number.isFinite(o)&&e-o<exports.WORKFLOW_RUN_LEGACY_RECOVERY_GRACE_MS}function ee(a,e=Date.now()){if(a.status!==void 0)return a;let t;if(a.current_node===""){t="pending"}else if(a.auto_run){t="running"}else if(lt(a,e)){t="retrying"}else{t="pending"}return{...a,status:t}}function Ue(a,e){if(a.status!=="failed")return false;if(!a.current_node)return false;const t=e.nodes?.[a.current_node];if(!t)return false;return t.type!=="error"}class ct{constructor(e=".crewx/workflow-runs",t,o){this.runsDir=e;this.sleep=o?.sleep??ze;this.onRunPersisted=o?.onRunPersisted;const r=t||process.env.CREWX_WORKSPACE||process.cwd();this.resolvedDir=W.resolve(r,this.runsDir);this.projectRoot=W.resolve(r);const s=R.existsSync(W.join(this.projectRoot,"crewx.yaml"))||R.existsSync(W.join(this.projectRoot,"crewx.yml"));if(!s){console.warn(`[RunManager] projectRoot "${this.projectRoot}" has no crewx.yaml/crewx.yml \u2014 agent_task nodes may fail to resolve agents.`)}}ensureRunsDir(){if(!R.existsSync(this.resolvedDir)){R.mkdirSync(this.resolvedDir,{recursive:true})}}validateExecId(e){if(/[/\\]|\.\./.test(e)){throw new Error(`Invalid execution ID: "${e}"`)}}isRunCancelled(e){try{return this.readRunRaw(e)?.status==="cancelled"}catch{return false}}processCancellation(e){return{key:Te(this.resolvedDir,e),isCancelled:()=>this.isRunCancelled(e)}}readRunRaw(e){const t=W.join(this.resolvedDir,`${e}.json`);if(!R.existsSync(t))return null;try{return JSON.parse(R.readFileSync(t,"utf-8"))}catch(o){throw new Error(`Failed to parse execution file ${t}: ${o.message}`)}}readRun(e){this.validateExecId(e);const t=this.readRunRaw(e);return t?ee(t):null}isLegacyStatuslessStale(e){if(e.status!==void 0||e.auto_run||e.manual_resume||!e.current_node||!e.audit?.length){return false}const t=Date.parse(e.updated_at);if(!Number.isFinite(t)||Date.now()-t<exports.WORKFLOW_RUN_LEGACY_RECOVERY_GRACE_MS){return false}const o=this.loadWorkflowYaml(e.workflow_file);const r=o?.workflows[e.workflow_id];const s=r?.nodes?.[e.current_node];return Boolean(s&&Ae.has(s.type))}withRunLock(e,t){this.validateExecId(e);const o=W.join(this.resolvedDir,`${e}.json`);if(!R.existsSync(o)){throw new Error(`Execution not found: ${e}`)}const r=new le.FileLock(o);r.acquire();try{const s=this.readRunRaw(e);if(!s)throw new Error(`Execution not found: ${e}`);return t(ee(s))}finally{r.release()}}hasStaleRunner(e){if(e.auto_run)return!De(e.auto_run.pid);return this.isLegacyStatuslessStale(e)}recoverStaleRunner(e){const t=W.join(this.resolvedDir,`${e}.json`);const o=new le.FileLock(t);o.acquire();try{const r=this.readRunRaw(e);if(!r||!this.hasStaleRunner(r))return r?ee(r):null;if(Re(r.status)){delete r.auto_run;delete r.manual_resume;this.saveRun(r);return r}const s=r.auto_run;const i=s?.node_id||r.current_node;const n=new Date().toISOString();const c=(0,d.buildNodeFailure)({failedNode:i,attempt:(0,d.readNodeAttempt)(r,i),causeCode:"INTERRUPTED",exitCode:null,signal:null,errorOrigin:null});V(r,c,exports.WORKFLOW_RUN_INTERRUPTED_ERROR);delete r.auto_run;delete r.manual_resume;Y(r,c,"interrupted","auto",n);this.saveRun(r);return r}finally{o.release()}}loadRun(e){this.validateExecId(e);const t=this.readRunRaw(e);if(!t||!this.hasStaleRunner(t))return t?ee(t):null;return this.recoverStaleRunner(e)}saveRun(e){this.ensureRunsDir();e.updated_at=new Date().toISOString();const t=W.join(this.resolvedDir,`${e.id}.json`);let o;try{o=R.statSync(t).mode&4095}catch(c){if(c.code!=="ENOENT")throw c}const r=W.join(this.resolvedDir,`.${e.id}.tmp-${process.pid}-${(0,N.generateId)("wfr")}`);const s=JSON.stringify(e,null,2)+"\n";let i;let n=false;try{i=R.openSync(r,"wx");n=true;R.writeFileSync(i,s,"utf-8");if(o!==void 0)R.fchmodSync(i,o);R.closeSync(i);i=void 0;R.renameSync(r,t);n=false}finally{if(i!==void 0){try{R.closeSync(i)}catch{}}if(n){try{R.unlinkSync(r)}catch{}}}if(!this.onRunPersisted)return;try{this.onRunPersisted(e)}catch(c){console.warn(`[RunManager] onRunPersisted failed for ${e.id}: ${J(c)}`)}}atomicUpdate(e,t){this.validateExecId(e);this.ensureRunsDir();const o=W.join(this.resolvedDir,`${e}.json`);const r=new le.FileLock(o);r.acquire();try{const s=this.readRun(e);if(!s)throw new Error(`Execution not found: ${e}`);t(s);this.saveRun(s);return s}finally{r.release()}}async atomicUpdateAsync(e,t){this.validateExecId(e);this.ensureRunsDir();const o=W.join(this.resolvedDir,`${e}.json`);const r=new le.FileLock(o);await r.acquireAsync();try{const s=this.readRun(e);if(!s)throw new Error(`Execution not found: ${e}`);t(s);this.saveRun(s);return s}finally{r.release()}}async reserveNodeAttempt(e,t){let o=1;await this.atomicUpdateAsync(e,r=>{if(r.status==="cancelled"){o=(0,d.readNodeAttempt)(r,t);return}const s=r.node_attempts?.[t];const i=typeof s==="number"&&Number.isInteger(s)&&s>0?s:0;o=i+1;r.node_attempts=r.node_attempts??{};r.node_attempts[t]=o});return o}clearNodeAttempt(e,t){if(!e.node_attempts)return;delete e.node_attempts[t];if(Object.keys(e.node_attempts).length===0)delete e.node_attempts}loadWorkflowYaml(e){if(!R.existsSync(e))return null;const t=R.readFileSync(e,"utf-8");const o=Me.load(t);if(!o||!o.workflows||typeof o.workflows!=="object")return null;return o}getNodeType(e,t,o){const r=this.loadWorkflowYaml(e);const s=r?.workflows[t];const i=s?.nodes?.[o];return typeof i?.type==="string"?i.type:null}start(e,t,o){const r=this.loadWorkflowYaml(e);if(!r){throw new Error(`Failed to load workflow: ${e}`)}const s=Object.entries(r.workflows);if(s.length===0){throw new Error(`No workflows found in ${e}`)}let i;let n;if(t){if(!r.workflows[t]){throw new Error(`Workflow "${t}" not found in ${e}`)}i=t;n=r.workflows[t]}else{[i,n]=s[0]}const c=(0,N.generateId)("wfr");const p=n.state?JSON.parse(JSON.stringify(n.state)):{};const l=o??{};const f={...p,...l};const m={id:c,workflow_id:i,workflow_file:e,created_at:new Date().toISOString(),updated_at:new Date().toISOString(),current_node:"",completed_nodes:[],state:f,initial_state:{...p,...l},status:"pending"};this.saveRun(m);return m}getState(e){const t=this.loadRun(e);if(!t){throw new Error(`Execution not found: ${e}`)}return t.state}setState(e,t){this.atomicUpdate(e,o=>{Object.assign(o.state,t)})}moveNode(e,t){return this.atomicUpdate(e,o=>{if(o.status==="cancelled")return;if(o.current_node&&!o.completed_nodes.includes(o.current_node)){o.completed_nodes.push(o.current_node)}o.current_node=t})}async executeNode(e,t,o){const r=this.loadRun(e);if(!r){throw new Error(`Execution not found: ${e}`)}if(r.status==="cancelled"){throw(0,d.createWorkflowFailureError)("CANCELLED",`Workflow run "${e}" is already cancelled`)}const s=this.loadWorkflowYaml(r.workflow_file);if(!s){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Failed to load workflow file: ${r.workflow_file}`)}const i=s.workflows[r.workflow_id];if(!i){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Workflow "${r.workflow_id}" not found in ${r.workflow_file}`)}const n=i.nodes?.[t];if(!n){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Node "${t}" not found in workflow "${r.workflow_id}"`)}const c=r.current_node;const p={};const l=[];let f=t;let m;let _;let S;let C;const w=o?.trigger??"manual";const k=new Date;let F=null;let H=null;let X=null;let q;let Z;const I=u=>{F=u.status;H=u.signal;X=u.errorOrigin;q=Ye(u)};const B=async()=>{const u=(0,N.generateId)("tsk");await this.atomicUpdateAsync(e,h=>{h.tasks=h.tasks??{};h.tasks[t]=u});C=u;return u};const Q=u=>{const h=new Date;return{exec_id:r.id,node_id:t,type:n.type,summary:Je(n),started_at:k.toISOString(),ended_at:h.toISOString(),duration_ms:h.getTime()-k.getTime(),exit_code:F,...u?{error:u}:{},trigger:w}};const g=await this.reserveNodeAttempt(e,t);try{switch(n.type){case"agent_task":{if(!n.agent){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Node "${t}" is agent_task but has no agent defined`)}const u={...r.state,state:r.state};const h=b(n.agent,u).trim();if(!h){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Node "${t}": assignee not resolved (agent field "${n.agent}" interpolated to empty)`)}const E=(0,U.normalizeCwd)(n.cwd?b(n.cwd,u):void 0,this.projectRoot);if(!E.ok){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Node "${t}": ${E.error}`)}const $=E.resolved;const v=(0,U.normalizeCwd)(void 0,this.projectRoot);if(!v.ok){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Node "${t}": ${v.error}`)}const y=v.resolved;const G=n.input?b(n.input,u):"";const j=process.env.CREWX_WORKFLOW_PREAMBLE==="off"?"":(0,K.buildWorkflowPreamble)({workflowId:r.workflow_id,nodeId:t,execId:r.id});const P=n.output_format==="json"?j+G+(0,K.buildJsonOutputSuffix)(n.output_schema,n.output_strict):j+G;const T=n.mode==="query"?"q":"x";const L=`@${h} ${P}`;if(o?.dryRun){console.log(`[dry-run] Would execute: crewx ${T} "@${h} ..."`);break}console.log(`Executing: @${h} (${T} mode)`);const A=Number(process.env[T==="q"?"CREWX_TIMEOUT_QUERY":"CREWX_TIMEOUT_EXECUTE"])||8*36e5;const O=Se({...process.env,CREWX_WORKSPACE:y,CREWX_WORKFLOW_EXEC_ID:r.id,CREWX_WORKFLOW_NODE_ID:t,CREWX_WORKFLOW_ID:r.workflow_id},y);const z=[...O.args,T,L];if(process.env.CREWX_WORKFLOW_THREAD==="on"){z.push(`--thread=workflow:${r.id}`)}const te={workflow:{exec_id:r.id,node_id:t,workflow_id:r.workflow_id}};z.push("--metadata",JSON.stringify(te));const Ne=(0,N.resolveWindowsSpawnProgram)({command:O.command,env:O.env,cwd:$,allowShellFallback:true,packageName:"crewx"});const re=(0,N.materializeWindowsSpawnProgram)(Ne,z);const je=await B();const D=await ue(re.command,re.argv,{cwd:$,env:(0,N.withoutCrewxRootContext)({...O.env,CREWX_TRACE_ID:je}),timeout:A,shell:re.shell??false,windowsHide:re.windowsHide,cancellation:this.processCancellation(e)});I(D);if(D.cancelled){throw(0,d.createWorkflowFailureError)("CANCELLED",`Agent execution cancelled for node "${t}"`)}if(D.timedOut){throw new Error(`Agent execution timed out after ${A}ms`)}if(D.error){throw D.error}const Oe=D.stderr.match(/crewx kill (tsk_\w+)/);if(Oe?.[1])C=Oe[1];if(D.status!==0){const ne=D.stderr.trim();throw new Error(`Agent execution failed (exit ${D.status}): ${ne}`)}const oe=D.stdout.trim();const ve=(0,K.detectWorkflowSignal)(oe);if(ve){throw new Error(`Node "${t}" agent signaled failure: ${ve.reason??"(no reason given)"}`)}if(n.output){if(n.output_format==="json"){const ne=n.output_retry??1;let se=null;let we=null;let Ce=P;for(let he=0;he<=ne;he++){let _e=oe;if(he>0){Ce=P+`
1
+ "use strict";var xe=exports&&exports.__createBinding||(Object.create?(function(a,e,t,o){if(o===void 0)o=t;var r=Object.getOwnPropertyDescriptor(e,t);if(!r||("get"in r?!e.__esModule:r.writable||r.configurable)){r={enumerable:true,get:function(){return e[t]}}}Object.defineProperty(a,o,r)}):(function(a,e,t,o){if(o===void 0)o=t;a[o]=e[t]}));var Ie=exports&&exports.__setModuleDefault||(Object.create?(function(a,e){Object.defineProperty(a,"default",{enumerable:true,value:e})}):function(a,e){a["default"]=e});var ie=exports&&exports.__importStar||(function(){var a=function(e){a=Object.getOwnPropertyNames||function(t){var o=[];for(var r in t)if(Object.prototype.hasOwnProperty.call(t,r))o[o.length]=r;return o};return a(e)};return function(e){if(e&&e.__esModule)return e;var t={};if(e!=null){for(var o=a(e),r=0;r<o.length;r++)if(o[r]!=="default")xe(t,e,o[r])}Ie(t,e);return t}})();Object.defineProperty(exports,"__esModule",{value:true});exports.RunManager=exports.WORKFLOW_RUN_LEGACY_RECOVERY_GRACE_MS=exports.WORKFLOW_RUN_INTERRUPTED_ERROR=exports.WORKFLOW_CANCEL_GRACE_MS=exports.SKILL_TASK_DEFAULT_TIMEOUT=exports.USER_CANCEL_REASON=exports.FileLock=void 0;exports.parseSetArgs=Qe;exports.readStdin=et;exports.interpolateTemplate=b;exports.safeEvaluate=ke;exports.safeEvaluateBranchCondition=de;exports.validateSafeBranchCondition=it;exports.normalizeRunStatus=ee;exports.isRetryableFailure=Ue;const R=ie(require("fs"));const W=ie(require("path"));const Me=ie(require("js-yaml"));const Ge=ie(require("child_process"));const N=require("@crewx/sdk");const d=require("./failure");const le=require("./file-lock");const K=require("./utils/output-format");const U=require("./utils/shell-security");const ce=require("./retry-policy");const Ke=require("./cancel-reason");var Ve=require("./file-lock");Object.defineProperty(exports,"FileLock",{enumerable:true,get:function(){return Ve.FileLock}});var qe=require("./cancel-reason");Object.defineProperty(exports,"USER_CANCEL_REASON",{enumerable:true,get:function(){return qe.USER_CANCEL_REASON}});exports.SKILL_TASK_DEFAULT_TIMEOUT=6e5;exports.WORKFLOW_CANCEL_GRACE_MS=2e3;const ze=100;exports.WORKFLOW_RUN_INTERRUPTED_ERROR="Workflow run interrupted: runner process exited before completion";exports.WORKFLOW_RUN_LEGACY_RECOVERY_GRACE_MS=10*6e4;const Xe=1e3;function Be(a){return new Promise(e=>{setTimeout(e,a)})}const $e=new Set(["agent_task","skill_task","shell_task","branch","expression","join","end","error"]);const Je=new Set(["branch","expression","end","error"]);function Se(a,e){const t=(0,N.resolveCrewxExecutable)({env:a,cwd:e});if(!t.ok){throw(0,d.createWorkflowFailureError)("CLI_UNRESOLVABLE",(0,N.formatCrewxExecutableFailure)(t))}return(0,N.createCrewxChildInvocation)(t,{baseEnv:a})}async function ye(a,e="SIGKILL"){if(!a.pid)return;try{const t=await(0,N.terminateProcessTree)(a.pid,{signal:e});if(t.status==="failed"||t.status==="not_found"){try{a.kill(e)}catch{}}}catch{try{a.kill(e)}catch{}}}async function ue(a,e,t){return new Promise(o=>{const r=()=>{try{return t.cancellation?.isCancelled()===true}catch{return false}};if(r()){o({status:null,stdout:"",stderr:"",timedOut:false,cancelled:true,signal:null,errorOrigin:null});return}let s;try{s=Ge.spawn(a,e,{cwd:t.cwd,env:t.env,stdio:[t.stdin!==void 0?"pipe":"ignore","pipe","pipe"],shell:t.shell??false,detached:process.platform!=="win32",windowsHide:t.windowsHide??true})}catch(g){o({status:null,stdout:"",stderr:"",timedOut:false,signal:null,errorOrigin:"spawn",error:g});return}let i="";let n="";let c=false;let p=false;let l=false;let f=false;let m;let _;let S;let C;let w;let k;const F=new N.ConsoleDecoder;const H=new N.ConsoleDecoder;let X=Promise.resolve();let q=Promise.resolve();const Z=()=>{if(S)clearTimeout(S);if(C)clearTimeout(C);if(w)clearInterval(w);if(t.cancellation&&k&&fe.get(t.cancellation.key)===k){fe.delete(t.cancellation.key)}};const I=g=>{if(c)return;c=true;Z();o(f?{...g,cancelled:true,timedOut:false}:g)};const B=()=>{if(c||f)return;f=true;void ye(s,"SIGTERM");C=setTimeout(()=>{if(!c)void ye(s,"SIGKILL")},exports.WORKFLOW_CANCEL_GRACE_MS);if(typeof C.unref==="function"){C.unref()}};if(t.cancellation){k={cancel:B};fe.set(t.cancellation.key,k);w=setInterval(()=>{if(r())B()},ze);if(typeof w.unref==="function")w.unref()}S=setTimeout(()=>{void ye(s);Q(()=>({status:null,stdout:i,stderr:n,timedOut:true,signal:null,errorOrigin:_?"stdin":m?"post_spawn":null,..._||m?{error:_??m}:{}}))},t.timeout);if(typeof S.unref==="function")S.unref();s.stdout?.on("data",g=>{X=X.then(async()=>{i+=await F.write(g)})});s.stderr?.on("data",g=>{q=q.then(async()=>{n+=await H.write(g)})});s.once("spawn",()=>{l=true});s.on("error",g=>{if(!l){I({status:null,stdout:i,stderr:n,timedOut:false,signal:null,errorOrigin:"spawn",error:g});return}m=m??g});if(t.stdin!==void 0){s.stdin?.on("error",g=>{_=_??g});try{s.stdin?.end(t.stdin)}catch(g){_=_??g}}const Q=g=>{if(c||p)return;p=true;void(async()=>{await Promise.all([X,q]);const[M,we]=await Promise.all([F.end(),H.end()]);i+=M;n+=we;I(g())})().catch(()=>I(g()))};s.on("close",(g,M)=>Q(()=>({status:g,stdout:i,stderr:n,timedOut:false,signal:M,errorOrigin:_?"stdin":m?"post_spawn":null,..._||m?{error:_??m}:{}})));if(r())B()})}const Ye=/^[A-Za-z0-9_.-]{1,128}$/;function be(a){if(a===void 0)return void 0;const e=Ye.exec(a);return e?.[0]===a?a:void 0}function He(a){switch(a.type){case"agent_task":{const e=be(a.agent);return e?`@${e} (${a.mode==="query"?"q":"x"})`:`agent_task (${a.mode==="query"?"q":"x"})`}case"skill_task":{const e=be(a.skill);return e?`skill ${e}`:"skill_task"}case"shell_task":return"shell_task";case"branch":return"branch";case"expression":return"expression";default:return"workflow node"}}function J(a){if(a instanceof Error)return a.message;return typeof a==="string"?a:"Node execution failed"}function Ze(a){return{status:a.status,timedOut:a.timedOut,signal:a.signal,errorOrigin:a.errorOrigin}}function Y(a,e,t,o,r=new Date().toISOString()){a.audit=a.audit??[];a.audit.push({exec_id:a.id,node_id:e.failedNode,type:t,summary:e.causeMessage,started_at:r,ended_at:r,duration_ms:0,exit_code:e.exitCode,error:e.causeMessage,trigger:o})}function V(a,e,t){a.status="failed";a.error=(0,d.sanitizeLocalRunError)(t,e.causeMessage);a.last_failure=e}async function Le(a,e,t,o){if(!a.output)return;const r=t.trim();if(a.output_format==="json"){let s;try{s=JSON.parse((0,K.extractJson)(r));if(a.output_schema){await(0,K.validateJsonSchema)(s,a.output_schema)}}catch(i){throw(0,d.createWorkflowFailureError)("OUTPUT_INVALID",`Node "${e}" output_format=json parse or validation failed: ${J(i)}`)}o[a.output]=s}else{o[a.output]=r}}function We(a){try{return JSON.parse(a)}catch{return a}}function Qe(a){const e={};for(let t=0;t<a.length;t++){const o=a[t];if(o==="--set"&&t+1<a.length){const r=a[t+1];const s=r.indexOf("=");if(s!==-1){const i=r.substring(0,s);const n=r.substring(s+1);e[i]=We(n)}t++}else if(o.startsWith("--set=")){const r=o.substring("--set=".length);const s=r.indexOf("=");if(s!==-1){const i=r.substring(0,s);const n=r.substring(s+1);e[i]=We(n)}}}return e}async function et(){const a=[];for await(const e of process.stdin){a.push(e)}return Buffer.concat(a).toString("utf-8").trimEnd()}function b(a,e){return a.replace(/\{\{([\w.]+)\}\}/g,(t,o)=>{const r=o.split(".");let s=e[r[0]];for(let i=1;i<r.length&&s!=null;i++){s=s[r[i]]}if(s===null||s===void 0)return"";if(typeof s==="object")return JSON.stringify(s,null,2);return String(s)})}const tt=new Set(["require","import","process","global","globalThis","Function","eval","__proto__","constructor","prototype"]);const rt=new Set(["__proto__","constructor","prototype"]);function ot(a){const e=[];let t=0;while(t<a.length){const o=a[t];if(/\s/.test(o)){t++;continue}if(/\d/.test(o)){let s="";while(t<a.length&&/[\d.]/.test(a[t])){s+=a[t];t++}e.push({type:"NUMBER",value:Number(s)});continue}if(o==="'"||o==='"'){const s=o;t++;let i="";while(t<a.length&&a[t]!==s){if(a[t]==="\\"){t++;i+=a[t]??""}else{i+=a[t]}t++}if(t>=a.length)throw new Error("Unterminated string literal");t++;e.push({type:"STRING",value:i});continue}if(/[a-zA-Z_]/.test(o)){let s="";while(t<a.length&&/\w/.test(a[t])){s+=a[t];t++}if(s==="true"||s==="false"){e.push({type:"BOOLEAN",value:s==="true"})}else{if(tt.has(s)){throw new Error(`Forbidden identifier in expression: "${s}"`)}e.push({type:"IDENT",value:s})}continue}if(t+1<a.length){const s=a.substring(t,t+2);const i={">=":"GTE","<=":"LTE","==":"EQ","!=":"NEQ","&&":"AND","||":"OR"};if(i[s]){e.push({type:i[s],value:s});t+=2;continue}}const r={"+":"PLUS","-":"MINUS","*":"STAR","/":"SLASH","%":"PERCENT",">":"GT","<":"LT","!":"NOT","?":"QUESTION",":":"COLON","(":"LPAREN",")":"RPAREN"};if(r[o]){e.push({type:r[o],value:o});t++;continue}if(o==="."||o==="["||o==="]"){throw new Error(`Forbidden character in expression: "${o}" (property access not allowed)`)}throw new Error(`Unexpected character in expression: "${o}"`)}e.push({type:"EOF",value:""});return e}class nt{constructor(e,t){this.tokens=e;this.state=t;this.pos=0}peek(){return this.tokens[this.pos]}advance(){return this.tokens[this.pos++]}expect(e){const t=this.advance();if(t.type!==e)throw new Error(`Expected ${e}, got ${t.type}`);return t}parse(){const e=this.parseTernary();if(this.peek().type!=="EOF"){throw new Error(`Unexpected token after expression: ${this.peek().type}`)}return e}parseTernary(){const e=this.parseOr();if(this.peek().type==="QUESTION"){this.advance();const t=this.parseTernary();this.expect("COLON");const o=this.parseTernary();return e?t:o}return e}parseOr(){let e=this.parseAnd();while(this.peek().type==="OR"){this.advance();const t=this.parseAnd();e=e||t}return e}parseAnd(){let e=this.parseEquality();while(this.peek().type==="AND"){this.advance();const t=this.parseEquality();e=e&&t}return e}parseEquality(){let e=this.parseComparison();while(this.peek().type==="EQ"||this.peek().type==="NEQ"){const t=this.advance().type;const o=this.parseComparison();e=t==="EQ"?e==o:e!=o}return e}parseComparison(){let e=this.parseAdditive();while(["GT","LT","GTE","LTE"].includes(this.peek().type)){const t=this.advance().type;const o=this.parseAdditive();switch(t){case"GT":e=e>o;break;case"LT":e=e<o;break;case"GTE":e=e>=o;break;case"LTE":e=e<=o;break}}return e}parseAdditive(){let e=this.parseMultiplicative();while(this.peek().type==="PLUS"||this.peek().type==="MINUS"){const t=this.advance().type;const o=this.parseMultiplicative();if(t==="PLUS"){if(typeof e==="string"||typeof o==="string"){e=String(e)+String(o)}else{e=e+o}}else{e=e-o}}return e}parseMultiplicative(){let e=this.parseUnary();while(["STAR","SLASH","PERCENT"].includes(this.peek().type)){const t=this.advance().type;const o=this.parseUnary();switch(t){case"STAR":e=e*o;break;case"SLASH":e=e/o;break;case"PERCENT":e=e%o;break}}return e}parseUnary(){if(this.peek().type==="NOT"){this.advance();return!this.parseUnary()}if(this.peek().type==="MINUS"){this.advance();return-this.parseUnary()}return this.parsePrimary()}parsePrimary(){const e=this.peek();switch(e.type){case"NUMBER":this.advance();return e.value;case"STRING":this.advance();return e.value;case"BOOLEAN":this.advance();return e.value;case"IDENT":{this.advance();const t=e.value;const o=this.state[t];if(o===void 0||o===null)return 0;if(typeof o==="boolean"||typeof o==="number")return o;const r=String(o);if(r==="")return"";const s=Number(r);return isNaN(s)?r:s}case"LPAREN":{this.advance();const t=this.parseTernary();this.expect("RPAREN");return t}default:throw new Error(`Unexpected token: ${e.type}`)}}}function ke(a,e){const t=ot(a);const o=new nt(t,e);return o.parse()}function Fe(a,e){const t=e.split(".");let o=a;for(const r of t){if(rt.has(r)){throw new Error(`Forbidden state path segment in expression: "${r}"`)}if(!r||o===null||o===void 0||typeof o!=="object"){return void 0}o=o[r]}return o}function st(a){return a.replace(/\\'/g,"'").replace(/\\"/g,'"').replace(/\\\\/g,"\\")}function at(a,e){const t={};let o=0;const r=n=>{const c=`__state_${o++}`;t[c]=n;return c};let s=a.replace(/!==/g,"!=").replace(/===/g,"==");const i=n=>{s=s.replace(n,(c,p,l)=>{const f=Fe(e,p);return r(String(f??"").includes(st(l)))})};i(/\bstate\.([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)\.includes\(\s*'((?:\\.|[^'\\])*)'\s*\)/g);i(/\bstate\.([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)\.includes\(\s*"((?:\\.|[^"\\])*)"\s*\)/g);s=s.replace(/\bstate\.([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)/g,(n,c)=>r(Fe(e,c)));s=s.replace(/\bNumber\(\s*([A-Za-z_]\w*)\s*\)/g,"$1");s=s.replace(/\bparseInt\(\s*([A-Za-z_]\w*(?:\s*\|\|\s*(?:'[^']*'|"[^"]*"))?)\s*\)/g,"($1)");return{expr:s,scope:t}}function de(a,e){const t=at(a,e);return ke(t.expr,{...e,...t.scope})}function it(a){de(a,{})}function lt(a){const e=a.state["__engine"];if(!e||typeof e!=="object")return{visitCounts:{},iterations:0};const t=e;const o=t["visitCounts"];const r={};if(o&&typeof o==="object"){for(const[i,n]of Object.entries(o)){if(typeof n==="number"&&Number.isFinite(n))r[i]=n}}const s=typeof t["iterations"]==="number"&&Number.isFinite(t["iterations"])?t["iterations"]:0;return{visitCounts:r,iterations:s}}const Re=new Set;const fe=new Map;function Te(a,e){return`${a}::${e}`}function ct(a,e){fe.get(Te(a,e))?.cancel()}function De(a){if(!Number.isInteger(a)||a<=0)return false;if(a===process.pid)return true;try{process.kill(a,0);return true}catch(e){return e.code==="EPERM"}}function pe(a){return a==="completed"||a==="failed"||a==="cancelled"}function ut(a,e){if(a.status!==void 0||a.auto_run)return false;const t=a.audit?.at(-1);if(t?.type!=="retry")return false;const o=Date.parse(a.updated_at);return Number.isFinite(o)&&e-o<exports.WORKFLOW_RUN_LEGACY_RECOVERY_GRACE_MS}function ee(a,e=Date.now()){if(a.status!==void 0)return a;let t;if(a.current_node===""){t="pending"}else if(a.auto_run){t="running"}else if(ut(a,e)){t="retrying"}else{t="pending"}return{...a,status:t}}function Ue(a,e){if(a.status!=="failed")return false;if(!a.current_node)return false;const t=e.nodes?.[a.current_node];if(!t)return false;return t.type!=="error"}class dt{constructor(e=".crewx/workflow-runs",t,o){this.runsDir=e;this.sleep=o?.sleep??Be;this.onRunPersisted=o?.onRunPersisted;const r=t||process.env.CREWX_WORKSPACE||process.cwd();this.resolvedDir=W.resolve(r,this.runsDir);this.projectRoot=W.resolve(r);const s=R.existsSync(W.join(this.projectRoot,"crewx.yaml"))||R.existsSync(W.join(this.projectRoot,"crewx.yml"));if(!s){console.warn(`[RunManager] projectRoot "${this.projectRoot}" has no crewx.yaml/crewx.yml \u2014 agent_task nodes may fail to resolve agents.`)}}ensureRunsDir(){if(!R.existsSync(this.resolvedDir)){R.mkdirSync(this.resolvedDir,{recursive:true})}}validateExecId(e){if(/[/\\]|\.\./.test(e)){throw new Error(`Invalid execution ID: "${e}"`)}}isRunCancelled(e){try{return this.readRunRaw(e)?.status==="cancelled"}catch{return false}}processCancellation(e){return{key:Te(this.resolvedDir,e),isCancelled:()=>this.isRunCancelled(e)}}readRunRaw(e){const t=W.join(this.resolvedDir,`${e}.json`);if(!R.existsSync(t))return null;try{return JSON.parse(R.readFileSync(t,"utf-8"))}catch(o){throw new Error(`Failed to parse execution file ${t}: ${o.message}`)}}readRun(e){this.validateExecId(e);const t=this.readRunRaw(e);return t?ee(t):null}isLegacyStatuslessStale(e){if(e.status!==void 0||e.auto_run||e.manual_resume||!e.current_node||!e.audit?.length){return false}const t=Date.parse(e.updated_at);if(!Number.isFinite(t)||Date.now()-t<exports.WORKFLOW_RUN_LEGACY_RECOVERY_GRACE_MS){return false}const o=this.loadWorkflowYaml(e.workflow_file);const r=o?.workflows[e.workflow_id];const s=r?.nodes?.[e.current_node];return Boolean(s&&$e.has(s.type))}withRunLock(e,t){this.validateExecId(e);const o=W.join(this.resolvedDir,`${e}.json`);if(!R.existsSync(o)){throw new Error(`Execution not found: ${e}`)}const r=new le.FileLock(o);r.acquire();try{const s=this.readRunRaw(e);if(!s)throw new Error(`Execution not found: ${e}`);return t(ee(s))}finally{r.release()}}hasStaleRunner(e){if(e.auto_run)return!De(e.auto_run.pid);return this.isLegacyStatuslessStale(e)}recoverStaleRunner(e){const t=W.join(this.resolvedDir,`${e}.json`);const o=new le.FileLock(t);o.acquire();try{const r=this.readRunRaw(e);if(!r||!this.hasStaleRunner(r))return r?ee(r):null;if(pe(r.status)){delete r.auto_run;delete r.manual_resume;this.saveRun(r);return r}const s=r.auto_run;const i=s?.node_id||r.current_node;const n=new Date().toISOString();const c=(0,d.buildNodeFailure)({failedNode:i,attempt:(0,d.readNodeAttempt)(r,i),causeCode:"INTERRUPTED",exitCode:null,signal:null,errorOrigin:null});V(r,c,exports.WORKFLOW_RUN_INTERRUPTED_ERROR);delete r.auto_run;delete r.manual_resume;Y(r,c,"interrupted","auto",n);this.saveRun(r);return r}finally{o.release()}}loadRun(e){this.validateExecId(e);const t=this.readRunRaw(e);if(!t||!this.hasStaleRunner(t))return t?ee(t):null;return this.recoverStaleRunner(e)}saveRun(e){this.ensureRunsDir();e.updated_at=new Date().toISOString();const t=W.join(this.resolvedDir,`${e.id}.json`);let o;try{o=R.statSync(t).mode&4095}catch(c){if(c.code!=="ENOENT")throw c}const r=W.join(this.resolvedDir,`.${e.id}.tmp-${process.pid}-${(0,N.generateId)("wfr")}`);const s=JSON.stringify(e,null,2)+"\n";let i;let n=false;try{i=R.openSync(r,"wx");n=true;R.writeFileSync(i,s,"utf-8");if(o!==void 0)R.fchmodSync(i,o);R.closeSync(i);i=void 0;R.renameSync(r,t);n=false}finally{if(i!==void 0){try{R.closeSync(i)}catch{}}if(n){try{R.unlinkSync(r)}catch{}}}if(!this.onRunPersisted)return;try{this.onRunPersisted(e)}catch(c){console.warn(`[RunManager] onRunPersisted failed for ${e.id}: ${J(c)}`)}}atomicUpdate(e,t){this.validateExecId(e);this.ensureRunsDir();const o=W.join(this.resolvedDir,`${e}.json`);const r=new le.FileLock(o);r.acquire();try{const s=this.readRun(e);if(!s)throw new Error(`Execution not found: ${e}`);t(s);this.saveRun(s);return s}finally{r.release()}}async atomicUpdateAsync(e,t){this.validateExecId(e);this.ensureRunsDir();const o=W.join(this.resolvedDir,`${e}.json`);const r=new le.FileLock(o);await r.acquireAsync();try{const s=this.readRun(e);if(!s)throw new Error(`Execution not found: ${e}`);t(s);this.saveRun(s);return s}finally{r.release()}}async reserveNodeAttempt(e,t){let o=1;await this.atomicUpdateAsync(e,r=>{if(r.status==="cancelled"){o=(0,d.readNodeAttempt)(r,t);return}const s=r.node_attempts?.[t];const i=typeof s==="number"&&Number.isInteger(s)&&s>0?s:0;o=i+1;r.node_attempts=r.node_attempts??{};r.node_attempts[t]=o});return o}clearNodeAttempt(e,t){if(!e.node_attempts)return;delete e.node_attempts[t];if(Object.keys(e.node_attempts).length===0)delete e.node_attempts}loadWorkflowYaml(e){if(!R.existsSync(e))return null;const t=R.readFileSync(e,"utf-8");const o=Me.load(t);if(!o||!o.workflows||typeof o.workflows!=="object")return null;return o}getNodeType(e,t,o){const r=this.loadWorkflowYaml(e);const s=r?.workflows[t];const i=s?.nodes?.[o];return typeof i?.type==="string"?i.type:null}start(e,t,o){const r=this.loadWorkflowYaml(e);if(!r){throw new Error(`Failed to load workflow: ${e}`)}const s=Object.entries(r.workflows);if(s.length===0){throw new Error(`No workflows found in ${e}`)}let i;let n;if(t){if(!r.workflows[t]){throw new Error(`Workflow "${t}" not found in ${e}`)}i=t;n=r.workflows[t]}else{[i,n]=s[0]}const c=(0,N.generateId)("wfr");const p=n.state?JSON.parse(JSON.stringify(n.state)):{};const l=o??{};const f={...p,...l};const m={id:c,workflow_id:i,workflow_file:e,created_at:new Date().toISOString(),updated_at:new Date().toISOString(),current_node:"",completed_nodes:[],state:f,initial_state:{...p,...l},status:"pending"};this.saveRun(m);return m}getState(e){const t=this.loadRun(e);if(!t){throw new Error(`Execution not found: ${e}`)}return t.state}setState(e,t){this.atomicUpdate(e,o=>{Object.assign(o.state,t)})}moveNode(e,t){return this.atomicUpdate(e,o=>{if(o.status==="cancelled")return;if(o.current_node&&!o.completed_nodes.includes(o.current_node)){o.completed_nodes.push(o.current_node)}o.current_node=t})}async executeNode(e,t,o){const r=this.loadRun(e);if(!r){throw new Error(`Execution not found: ${e}`)}if(r.status==="cancelled"){throw(0,d.createWorkflowFailureError)("CANCELLED",`Workflow run "${e}" is already cancelled`)}const s=this.loadWorkflowYaml(r.workflow_file);if(!s){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Failed to load workflow file: ${r.workflow_file}`)}const i=s.workflows[r.workflow_id];if(!i){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Workflow "${r.workflow_id}" not found in ${r.workflow_file}`)}const n=i.nodes?.[t];if(!n){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Node "${t}" not found in workflow "${r.workflow_id}"`)}const c=r.current_node;const p={};const l=[];let f=t;let m;let _;let S;let C;const w=o?.trigger??"manual";const k=new Date;let F=null;let H=null;let X=null;let q;let Z;const I=u=>{F=u.status;H=u.signal;X=u.errorOrigin;q=Ze(u)};const B=async()=>{const u=(0,N.generateId)("tsk");await this.atomicUpdateAsync(e,h=>{h.tasks=h.tasks??{};h.tasks[t]=u});C=u;return u};const Q=u=>{const h=new Date;return{exec_id:r.id,node_id:t,type:n.type,summary:He(n),started_at:k.toISOString(),ended_at:h.toISOString(),duration_ms:h.getTime()-k.getTime(),exit_code:F,...u?{error:u}:{},trigger:w}};const g=await this.reserveNodeAttempt(e,t);try{switch(n.type){case"agent_task":{if(!n.agent){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Node "${t}" is agent_task but has no agent defined`)}const u={...r.state,state:r.state};const h=b(n.agent,u).trim();if(!h){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Node "${t}": assignee not resolved (agent field "${n.agent}" interpolated to empty)`)}const y=(0,U.normalizeCwd)(n.cwd?b(n.cwd,u):void 0,this.projectRoot);if(!y.ok){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Node "${t}": ${y.error}`)}const A=y.resolved;const v=(0,U.normalizeCwd)(void 0,this.projectRoot);if(!v.ok){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Node "${t}": ${v.error}`)}const E=v.resolved;const G=n.input?b(n.input,u):"";const j=process.env.CREWX_WORKFLOW_PREAMBLE==="off"?"":(0,K.buildWorkflowPreamble)({workflowId:r.workflow_id,nodeId:t,execId:r.id});const P=n.output_format==="json"?j+G+(0,K.buildJsonOutputSuffix)(n.output_schema,n.output_strict):j+G;const T=n.mode==="query"?"q":"x";const L=`@${h} ${P}`;if(o?.dryRun){console.log(`[dry-run] Would execute: crewx ${T} "@${h} ..."`);break}console.log(`Executing: @${h} (${T} mode)`);const $=Number(process.env[T==="q"?"CREWX_TIMEOUT_QUERY":"CREWX_TIMEOUT_EXECUTE"])||8*36e5;const O=Se({...process.env,CREWX_WORKSPACE:E,CREWX_WORKFLOW_EXEC_ID:r.id,CREWX_WORKFLOW_NODE_ID:t,CREWX_WORKFLOW_ID:r.workflow_id},E);const z=[...O.args,T,L];if(process.env.CREWX_WORKFLOW_THREAD==="on"){z.push(`--thread=workflow:${r.id}`)}const te={workflow:{exec_id:r.id,node_id:t,workflow_id:r.workflow_id}};z.push("--metadata",JSON.stringify(te));const Ne=(0,N.resolveWindowsSpawnProgram)({command:O.command,env:O.env,cwd:A,allowShellFallback:true,packageName:"crewx"});const re=(0,N.materializeWindowsSpawnProgram)(Ne,z);const je=await B();const D=await ue(re.command,re.argv,{cwd:A,env:(0,N.withoutCrewxRootContext)({...O.env,CREWX_TRACE_ID:je}),timeout:$,shell:re.shell??false,windowsHide:re.windowsHide,cancellation:this.processCancellation(e)});I(D);if(D.cancelled){throw(0,d.createWorkflowFailureError)("CANCELLED",`Agent execution cancelled for node "${t}"`)}if(D.timedOut){throw new Error(`Agent execution timed out after ${$}ms`)}if(D.error){throw D.error}const Oe=D.stderr.match(/crewx kill (tsk_\w+)/);if(Oe?.[1])C=Oe[1];if(D.status!==0){const ne=D.stderr.trim();throw new Error(`Agent execution failed (exit ${D.status}): ${ne}`)}const oe=D.stdout.trim();const ve=(0,K.detectWorkflowSignal)(oe);if(ve){throw new Error(`Node "${t}" agent signaled failure: ${ve.reason??"(no reason given)"}`)}if(n.output){if(n.output_format==="json"){const ne=n.output_retry??1;let se=null;let he=null;let Ce=P;for(let _e=0;_e<=ne;_e++){let me=oe;if(_e>0){Ce=P+`
2
2
 
3
- ## \u26A0\uFE0F PREVIOUS OUTPUT FAILED JSON.parse: ${we.message}
4
- Return STRICT JSON ONLY. First char "{", last char "}". No prose, no fences.`;const me=`@${h} ${Ce}`;const ge=[...O.args,T,me];if(process.env.CREWX_WORKFLOW_THREAD==="on"){ge.push(`--thread=workflow:${r.id}`)}ge.push("--metadata",JSON.stringify(te));const ae=(0,N.materializeWindowsSpawnProgram)(Ne,ge);const Pe=await B();const x=await ue(ae.command,ae.argv,{cwd:$,env:(0,N.withoutCrewxRootContext)({...O.env,CREWX_TRACE_ID:Pe}),timeout:A,shell:ae.shell??false,windowsHide:ae.windowsHide,cancellation:this.processCancellation(e)});I(x);if(x.cancelled){throw(0,d.createWorkflowFailureError)("CANCELLED",`Agent retry cancelled for node "${t}"`)}if(x.timedOut)throw new Error(`Agent retry timed out after ${A}ms`);if(x.error)throw x.error;if(x.status!==0){throw new Error(`Agent retry failed (exit ${x.status}): ${x.stderr.trim()}`)}_e=x.stdout.trim();const $e=(0,K.detectWorkflowSignal)(_e);if($e){throw new Error(`Node "${t}" agent signaled failure: ${$e.reason??"(no reason given)"}`)}}try{se=JSON.parse((0,K.extractJson)(_e));if(n.output_schema){await(0,K.validateJsonSchema)(se,n.output_schema)}break}catch(me){we=me}}if(se===null){throw(0,d.createWorkflowFailureError)("OUTPUT_INVALID",`Node "${t}" output_format=json parse failed after ${ne+1} attempt(s): ${we.message}`)}p[n.output]=se;console.log(`Result stored in state.${n.output} (JSON object)`)}else{p[n.output]=oe;console.log(`Result stored in state.${n.output} (${oe.length} chars)`)}}break}case"skill_task":{if(!n.skill){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Node "${t}" is skill_task but has no skill defined`)}if(!U.SKILL_NAME_RE.test(n.skill)){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Node "${t}" has invalid skill name "${n.skill}" (must match ^[a-z0-9][a-z0-9-]*$)`)}const u={...r.state,state:r.state};const h=(n.args??[]).map(O=>b(String(O),u));const E=n.stdin!==void 0?b(String(n.stdin),u):void 0;const $=(0,U.normalizeCwd)(n.cwd?b(n.cwd,u):void 0,this.projectRoot);if(!$.ok){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Node "${t}": ${$.error}`)}const v=(0,U.normalizeCwd)(void 0,this.projectRoot);if(!v.ok){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Node "${t}": ${v.error}`)}const y=v.resolved;const G=n.timeout??exports.SKILL_TASK_DEFAULT_TIMEOUT;if(o?.dryRun){console.log(`[dry-run] Would execute: crewx skill ${n.skill} ${h.join(" ")}`.trim());break}console.log(`Executing skill: ${n.skill} ${h.join(" ")}`.trim());const j=Se({...process.env,CREWX_WORKSPACE:y,CREWX_WORKFLOW_EXEC_ID:r.id,CREWX_WORKFLOW_NODE_ID:t,CREWX_WORKFLOW_ID:r.workflow_id},y);const P=[...j.args,"skill",n.skill,...h];const T=(0,N.resolveWindowsSpawnProgram)({command:j.command,env:j.env,cwd:$.resolved,allowShellFallback:true,packageName:"crewx"});const L=(0,N.materializeWindowsSpawnProgram)(T,P);const A=await ue(L.command,L.argv,{cwd:$.resolved,env:j.env,timeout:G,shell:L.shell??false,windowsHide:L.windowsHide,stdin:E,cancellation:this.processCancellation(e)});I(A);if(A.cancelled){throw(0,d.createWorkflowFailureError)("CANCELLED",`skill_task "${t}" was cancelled`)}if(A.timedOut)throw new Error(`skill_task "${t}" timed out after ${G}ms`);if(A.error)throw A.error;if(A.status!==0){throw new Error(`skill_task "${t}" failed (exit ${A.status}): ${A.stderr.trim()}`)}await Le(n,t,A.stdout,p);if(n.output){console.log(`Result stored in state.${n.output}`)}break}case"shell_task":{const u=process.env.CREWX_WORKFLOW_SHELL==="1";const h=i.metadata?.shell_task_allowed===true;if(!u||!h){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID","shell_task is disabled. Set CREWX_WORKFLOW_SHELL=1 and metadata.shell_task_allowed=true to enable.")}const E=(0,U.validateShellCommand)(n.command);if(!E.ok){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Node "${t}": ${E.error}`)}const $=(0,U.validateEnvKeys)(n.env);if(!$.ok){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Node "${t}": ${$.error}`)}const v={...r.state,state:r.state};const y=n.command.map(z=>b(z,v));const G=(0,U.validateShellCommand)(y);if(!G.ok){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Node "${t}": ${G.error}`)}const j=n.env?Object.fromEntries(Object.entries(n.env).map(([z,te])=>[z,b(String(te),v)])):void 0;const P=(0,U.normalizeCwd)(n.cwd?b(n.cwd,v):void 0,this.projectRoot);if(!P.ok){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Node "${t}": ${P.error}`)}const T=n.timeout??exports.SKILL_TASK_DEFAULT_TIMEOUT;if(o?.dryRun){console.log(`[dry-run] Would execute: ${y.join(" ")}`);break}console.log(`Executing shell_task: ${y[0]} (${y.length-1} args)`);const L=(0,U.buildShellEnv)(j,process.env);L.CREWX_WORKFLOW_EXEC_ID=r.id;L.CREWX_WORKFLOW_NODE_ID=t;L.CREWX_WORKFLOW_ID=r.workflow_id;const A=n.stdin!==void 0?b(String(n.stdin),v):void 0;const O=await ue(y[0],y.slice(1),{cwd:P.resolved,env:L,timeout:T,shell:false,stdin:A,cancellation:this.processCancellation(e)});I(O);if(O.cancelled){throw(0,d.createWorkflowFailureError)("CANCELLED",`shell_task "${t}" was cancelled`)}if(O.timedOut)throw new Error(`shell_task "${t}" timed out after ${T}ms`);if(O.error)throw O.error;if(O.status!==0){throw new Error(`shell_task "${t}" failed (exit ${O.status}): ${O.stderr.trim()}`)}await Le(n,t,O.stdout,p);if(n.output){console.log(`Result stored in state.${n.output}`)}break}case"branch":{if(!n.condition){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Branch node "${t}" has no condition`)}const u=String(de(String(n.condition),r.state));const h=n.branches;const E=h?.[u]??n.default;if(!E){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Branch "${t}": no matching branch for "${u}" and no default`)}console.log(`Branch "${t}": condition="${n.condition}" \u2192 ${u} \u2192 ${E}`);l.push(t);f=E;break}case"parallel":{const u=n.branches;if(u&&Array.isArray(u)){console.log(`Parallel "${t}": branches = [${u.join(", ")}]`);console.log(` Enter each branch sequentially with: workflow run node <exec-id> <branch-node>`)}break}case"join":{console.log(`Join "${t}": completed_nodes = [${r.completed_nodes.join(", ")}]`);break}case"end":{m="completed";console.log(`End "${t}": workflow execution completed`);break}case"error":{m="failed";S="UNKNOWN";const u={...r.state,state:r.state};_=n.message?b(n.message,u):`Workflow failed at error node "${t}"`;console.log(`Error "${t}": workflow execution failed \u2014 ${_}`);break}case"expression":{if(!n.set||typeof n.set!=="object"){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Expression node "${t}" requires "set" field`)}for(const[u,h]of Object.entries(n.set)){const E=Ee(String(h),r.state);p[u]=String(E);console.log(` ${u} = ${E}`)}console.log(`Expression "${t}": set ${Object.keys(n.set).join(", ")}`);if(n.next){l.push(t);f=n.next}break}case"approval":{console.log(`Approval "${t}": manual intervention required (not auto-executed)`);break}default:console.log(`Unknown node type "${n.type}" for node "${t}"`)}if(n.fail_when&&!o?.dryRun&&(n.type==="agent_task"||n.type==="skill_task"||n.type==="shell_task")){const u=de(String(n.fail_when),{...r.state,...p});if(u===true||u==="true"){const h=n.output?p[n.output]:void 0;if(h&&typeof h==="object"&&!Array.isArray(h)){const E=h.reason;if(typeof E==="string"){const $=(0,N.redactTechnicalMessage)(E);const v=$?.split(/\r\n|\r|\n/u).find(y=>y.trim());if(v)Z=v.trim().slice(0,180)}}throw new Error(`Node "${t}" judged failed by fail_when: ${n.fail_when}`)}}}catch(u){const h=J(u);const E=(0,d.classifyWorkflowFailure)({errorCode:(0,d.getWorkflowFailureCode)(u),process:q});const $=(0,d.buildNodeFailure)({failedNode:t,attempt:g,causeCode:E,exitCode:E==="NODE_EXIT_NONZERO"&&typeof F==="number"?F:null,signal:E==="INTERRUPTED"?H:null,errorOrigin:X,timedOut:q?.timedOut??false});const v=Q(Z??$.causeMessage);await this.atomicUpdateAsync(e,y=>{if(y.status==="cancelled")return;Object.assign(y.state,p);delete y.manual_resume;y.current_node=t;V(y,$,Z??h);y.audit=y.audit??[];y.audit.push(v)});throw u}const M=S?(0,d.buildNodeFailure)({failedNode:t,attempt:g,causeCode:S,exitCode:null,signal:null,errorOrigin:null}):null;const pe=Q(M?.causeMessage);return this.atomicUpdateAsync(e,u=>{if(u.status==="cancelled")return;Object.assign(u.state,p);delete u.manual_resume;for(const h of l){if(!u.completed_nodes.includes(h)){u.completed_nodes.push(h)}}if(c&&c!==t&&!u.completed_nodes.includes(c)){u.completed_nodes.push(c)}u.current_node=f;if(M){V(u,M,_??M.causeMessage)}else{delete u.error;delete u.last_failure;this.clearNodeAttempt(u,t);if(m){u.status=m}else if(u.status!=="completed"){u.status="running"}}if(C){u.tasks=u.tasks??{};u.tasks[t]=C}u.audit=u.audit??[];u.audit.push(pe)})}async claimAutoRunLease(e,t,o){let r=false;const s=await this.atomicUpdateAsync(e,i=>{if(Re(i.status))return;const n=i.auto_run;const c=n?.pid===process.pid&&typeof n.token==="string";if(n&&!c&&De(n.pid)){throw new Error(`Workflow run "${e}" is already owned by another runner process`)}const p=o??(c?n.token:(0,N.generateId)("wfr"));if(!Re(i.status)){i.status="running"}if(!i.current_node)i.current_node=t;i.auto_run={pid:process.pid,token:p,node_id:t,started_at:c&&n.token===p?n.started_at:new Date().toISOString()};delete i.manual_resume;r=true});return r?s.auto_run?.token??null:null}async releaseAutoRunLease(e,t){await this.atomicUpdateAsync(e,o=>{if(o.auto_run?.token===t)delete o.auto_run})}async failAutoConfiguration(e,t,o){return this.atomicUpdateAsync(e,r=>{if(r.status==="cancelled")return;const s=(0,d.buildNodeFailure)({failedNode:t,attempt:(0,d.readNodeAttempt)(r,t),causeCode:"CONFIG_INVALID",exitCode:null,signal:null,errorOrigin:null});V(r,s,o);Y(r,s,"config","auto")})}async reserveAutoRetry(e,t,o,r,s){let i=false;const n=await this.atomicUpdateAsync(e,c=>{if(c.status==="cancelled")return;if(c.status!=="failed")return;if(c.auto_run?.token!==o)return;const p=(0,ce.decideRetry)({policy:r,run:c,nodeId:t});if(!p.retry||p.attempt===void 0)return;const l=new Date().toISOString();c.status="retrying";delete c.error;delete c.manual_resume;c.audit=c.audit??[];c.audit.push({exec_id:c.id,node_id:t,type:"retry",summary:`auto retry ${p.attempt+1}/${r.max+1} in ${s}ms`,started_at:l,ended_at:l,duration_ms:0,exit_code:null,trigger:"auto"});i=true});return{run:n,reserved:i}}async waitForRetry(e,t,o){const r=()=>{const c=this.loadRun(e);if(!c)throw new Error(`Execution not found: ${e}`);if(c.status==="cancelled"){return{run:c,outcome:"cancelled",reason:"run cancelled during retry delay"}}if(c.auto_run?.token!==t){return{run:c,outcome:"paused",reason:"auto-run lease lost during retry delay"}}if(c.status!=="retrying"){return{run:c,outcome:"paused",reason:"run state changed during retry delay"}}return null};let s=Math.max(0,o);while(s>0){const c=Math.min(s,qe);await this.sleep(c);s-=c;const p=r();if(p)return p}const i=r();if(i)return i;const n=this.loadRun(e);if(!n)throw new Error(`Execution not found: ${e}`);return{run:n,outcome:"ready"}}async runNodeWithRetry(e,t,o,r){let s=o;while(true){try{const i=await this.executeNode(e,t,{trigger:"auto"});if(i.status==="cancelled"){return{run:i,runnerToken:s,outcome:"cancelled",reason:i.error}}return{run:i,runnerToken:s,outcome:"success"}}catch(i){const n=this.loadRun(e);if(!n)throw new Error(`Execution not found: ${e}`);if(n.status==="cancelled"){return{run:n,runnerToken:s,outcome:"cancelled",reason:n.error}}if(n.status!=="failed"||!r){return{run:n,runnerToken:s,outcome:n.status==="failed"?"failed":"paused",reason:J(i)}}const c=(0,ce.decideRetry)({policy:r,run:n,nodeId:t});if(!c.retry||c.attempt===void 0){return{run:n,runnerToken:s,outcome:"failed",reason:J(i)}}const p=(0,ce.computeRetryDelayMs)(r,c.attempt);const l=await this.reserveAutoRetry(e,t,s,r,p);if(!l.reserved){const _=l.run;if(_.status==="cancelled"){return{run:_,runnerToken:s,outcome:"cancelled",reason:_.error}}return{run:_,runnerToken:s,outcome:_.status==="failed"?"failed":"paused",reason:J(i)}}const f=await this.waitForRetry(e,s,p);if(f.outcome!=="ready"){return{run:f.run,runnerToken:s,outcome:f.outcome,reason:f.reason}}const m=await this.claimAutoRunLease(e,t,s);if(!m){const _=this.loadRun(e);if(!_)throw new Error(`Execution not found: ${e}`);if(_.status==="cancelled"){return{run:_,runnerToken:s,outcome:"cancelled",reason:_.error}}return{run:_,runnerToken:s,outcome:"paused",reason:"run stopped before retry lease could be reclaimed"}}s=m}}}async runAuto(e){const t=`${this.resolvedDir}::${e}`;if(ke.has(t)){const r=this.loadRun(e);if(!r)throw new Error(`Execution not found: ${e}`);console.log(`runAuto "${e}": already in-flight \u2014 skipping duplicate auto-run (single-flight guard)`);return{run:r,outcome:"paused",reason:"execution already in-flight (single-flight guard)",executed:[]}}ke.add(t);let o;try{const r=this.loadRun(e);if(!r)throw new Error(`Execution not found: ${e}`);if(r.auto_run?.pid===process.pid){o=r.auto_run.token}const s=this.loadWorkflowYaml(r.workflow_file);if(!s){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Failed to load workflow file: ${r.workflow_file}`)}const i=s.workflows[r.workflow_id];if(!i){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Workflow "${r.workflow_id}" not found in ${r.workflow_file}`)}const n=[];if(r.status==="completed"||r.status==="failed"||r.status==="cancelled"){return{run:r,outcome:r.status==="completed"?"completed":r.status==="cancelled"?"cancelled":"failed",reason:`run already ${r.status}`,executed:n}}const c=Object.keys(i.nodes??{})[0];const p=typeof i.max_iterations==="number"&&i.max_iterations>0?i.max_iterations:30;let l=r;while(true){l=this.loadRun(e);if(l.status==="completed")return{run:l,outcome:"completed",executed:n};if(l.status==="failed")return{run:l,outcome:"failed",reason:l.error,executed:n};if(l.status==="cancelled")return{run:l,outcome:"cancelled",reason:l.error,executed:n};const f=l.current_node||c;if(!f){l=await this.atomicUpdateAsync(e,w=>{const k=(0,d.buildNodeFailure)({failedNode:w.current_node||"",attempt:(0,d.readNodeAttempt)(w,w.current_node||""),causeCode:"CONFIG_INVALID",exitCode:null,signal:null,errorOrigin:null});V(w,k,"workflow has no nodes");Y(w,k,"config","auto")});return{run:l,outcome:"failed",reason:l.error,executed:n}}const m=i.nodes[f];if(!m){l=await this.atomicUpdateAsync(e,w=>{w.current_node=f;const k=(0,d.buildNodeFailure)({failedNode:f,attempt:(0,d.readNodeAttempt)(w,f),causeCode:"CONFIG_INVALID",exitCode:null,signal:null,errorOrigin:null});V(w,k,`node "${f}" not found in workflow`);Y(w,k,"config","auto")});return{run:l,outcome:"failed",reason:l.error,executed:n}}const _=(0,ce.parseRetryPolicy)(m);if(!_.ok){l=await this.failAutoConfiguration(e,f,_.reason);if(l.status==="cancelled"){return{run:l,outcome:"cancelled",reason:l.error,executed:n}}return{run:l,outcome:"failed",reason:l.error,executed:n}}if(m.type==="approval"){if(l.current_node!==f){l=await this.atomicUpdateAsync(e,w=>{w.current_node=f})}return{run:l,outcome:"paused",reason:`approval node "${f}" requires manual decision`,executed:n}}if(m.type==="parallel"){if(l.current_node!==f){l=await this.atomicUpdateAsync(e,w=>{w.current_node=f})}return{run:l,outcome:"paused",reason:`parallel auto-run not implemented (node "${f}")`,executed:n}}if(!Ae.has(m.type)){if(l.current_node!==f){l=await this.atomicUpdateAsync(e,w=>{w.current_node=f})}return{run:l,outcome:"paused",reason:`unknown node type "${m.type}" (node "${f}")`,executed:n}}const S=await this.claimAutoRunLease(e,f,o);if(!S){l=this.loadRun(e);return{run:l,outcome:l.status==="cancelled"?"cancelled":"paused",reason:l.status==="cancelled"?l.error:"run stopped before auto-run lease could be claimed",executed:n}}o=S;l=await this.atomicUpdateAsync(e,w=>{if(w.status==="cancelled")return;const k=at(w);k.visitCounts[f]=(k.visitCounts[f]??0)+1;k.iterations+=1;w.state["__engine"]=k;if(k.visitCounts[f]>p||k.iterations>p){const F=(0,d.buildNodeFailure)({failedNode:f,attempt:(0,d.readNodeAttempt)(w,f),causeCode:"CONFIG_INVALID",exitCode:null,signal:null,errorOrigin:null});V(w,F,`auto runner exceeded max_iterations (${p}); possible loop at "${f}"`);Y(w,F,"config","auto")}});if(l.status==="cancelled"){return{run:l,outcome:"cancelled",reason:l.error,executed:n}}if(l.status==="failed"){return{run:l,outcome:"failed",reason:l.error,executed:n}}const C=await this.runNodeWithRetry(e,f,o,_.policy);l=C.run;o=C.runnerToken;if(C.outcome!=="success"){return{run:l,outcome:C.outcome,reason:C.reason,executed:n}}n.push(f);if(l.status==="completed")return{run:l,outcome:"completed",executed:n};if(l.status==="failed")return{run:l,outcome:"failed",reason:l.error,executed:n};if(l.status==="cancelled")return{run:l,outcome:"cancelled",reason:l.error,executed:n};if(!Xe.has(m.type)){const w=m.next;if(!w){return{run:l,outcome:"paused",reason:`node "${f}" has no "next"; stopping`,executed:n}}l=this.moveNode(e,w)}}}finally{if(o){try{await this.releaseAutoRunLease(e,o)}catch{}}ke.delete(t)}}cancelRun(e,t="Cancelled by user"){const o=this.atomicUpdate(e,r=>{if(r.status==="completed"||r.status==="failed"||r.status==="cancelled"){return}const s=new Date;const i=r.auto_run?.node_id||r.current_node||"";const n=(0,d.buildNodeFailure)({failedNode:i,attempt:(0,d.readNodeAttempt)(r,i),causeCode:"CANCELLED",exitCode:null,signal:null,errorOrigin:null});V(r,n,t);r.status="cancelled";delete r.auto_run;delete r.manual_resume;Y(r,n,"cancel","manual",s.toISOString())});if(o.status==="cancelled"){it(this.resolvedDir,e)}return o}reset(e){return this.atomicUpdate(e,t=>{t.state=JSON.parse(JSON.stringify(t.initial_state));t.current_node="";t.completed_nodes=[];t.status="pending";delete t.error;delete t.last_failure;delete t.node_attempts;delete t.auto_run;delete t.manual_resume})}retry(e,t={}){const o=this.loadRun(e);if(!o)throw new Error(`Execution not found: ${e}`);const r=this.loadWorkflowYaml(o.workflow_file);if(!r)throw new Error(`Failed to load workflow file: ${o.workflow_file}`);const s=r.workflows[o.workflow_id];if(!s)throw new Error(`Workflow "${o.workflow_id}" not found`);if(o.status!=="failed"){throw new Error(`Run "${e}" is not failed (status=${o.status??"running"}); only failed runs can be retried`)}if(!Ue(o,s)){throw new Error(`Run "${e}" failed at a non-retryable node "${o.current_node}" (error node); use reset to restart`)}return this.atomicUpdate(e,i=>{if(i.status!=="failed"){throw new Error(`Run "${e}" is not failed (status=${i.status??"pending"}); only failed runs can be retried`)}const n=i.current_node;if(n&&i.last_failure){const p=i.last_failure.attempt;const l=i.node_attempts?.[n];if(typeof p==="number"&&Number.isInteger(p)&&p>0&&(typeof l!=="number"||l<p)){i.node_attempts=i.node_attempts??{};i.node_attempts[n]=p}}i.status="retrying";delete i.error;delete i.manual_resume;i.retry_count=(i.retry_count??0)+1;i.audit=i.audit??[];const c=new Date().toISOString();if(t.autoRun===false){delete i.auto_run;i.manual_resume={node_id:n,requested_at:c}}else{i.auto_run={pid:process.pid,token:(0,N.generateId)("wfr"),node_id:n,started_at:c}}i.audit.push({exec_id:i.id,node_id:n,type:"retry",summary:`manual retry #${i.retry_count}`,started_at:c,ended_at:c,duration_ms:0,exit_code:null,trigger:"manual"})})}listRunIds(){try{return R.readdirSync(this.resolvedDir,{withFileTypes:true}).filter(e=>e.isFile()&&!e.name.startsWith(".")&&e.name.endsWith(".json")).map(e=>e.name.slice(0,-".json".length))}catch(e){if(e.code==="ENOENT")return[];throw e}}listRuns(e){this.ensureRunsDir();const t=R.readdirSync(this.resolvedDir).filter(r=>r.endsWith(".json"));const o=[];for(const r of t){try{const s=r.slice(0,-".json".length);const i=this.loadRun(s);if(!i)continue;if(e?.workflowId&&i.workflow_id!==e.workflowId)continue;o.push(i)}catch{}}o.sort((r,s)=>new Date(s.created_at).getTime()-new Date(r.created_at).getTime());return o}}exports.RunManager=ct;
3
+ ## \u26A0\uFE0F PREVIOUS OUTPUT FAILED JSON.parse: ${he.message}
4
+ Return STRICT JSON ONLY. First char "{", last char "}". No prose, no fences.`;const ge=`@${h} ${Ce}`;const Ee=[...O.args,T,ge];if(process.env.CREWX_WORKFLOW_THREAD==="on"){Ee.push(`--thread=workflow:${r.id}`)}Ee.push("--metadata",JSON.stringify(te));const ae=(0,N.materializeWindowsSpawnProgram)(Ne,Ee);const Pe=await B();const x=await ue(ae.command,ae.argv,{cwd:A,env:(0,N.withoutCrewxRootContext)({...O.env,CREWX_TRACE_ID:Pe}),timeout:$,shell:ae.shell??false,windowsHide:ae.windowsHide,cancellation:this.processCancellation(e)});I(x);if(x.cancelled){throw(0,d.createWorkflowFailureError)("CANCELLED",`Agent retry cancelled for node "${t}"`)}if(x.timedOut)throw new Error(`Agent retry timed out after ${$}ms`);if(x.error)throw x.error;if(x.status!==0){throw new Error(`Agent retry failed (exit ${x.status}): ${x.stderr.trim()}`)}me=x.stdout.trim();const Ae=(0,K.detectWorkflowSignal)(me);if(Ae){throw new Error(`Node "${t}" agent signaled failure: ${Ae.reason??"(no reason given)"}`)}}try{se=JSON.parse((0,K.extractJson)(me));if(n.output_schema){await(0,K.validateJsonSchema)(se,n.output_schema)}break}catch(ge){he=ge}}if(se===null){throw(0,d.createWorkflowFailureError)("OUTPUT_INVALID",`Node "${t}" output_format=json parse failed after ${ne+1} attempt(s): ${he.message}`)}p[n.output]=se;console.log(`Result stored in state.${n.output} (JSON object)`)}else{p[n.output]=oe;console.log(`Result stored in state.${n.output} (${oe.length} chars)`)}}break}case"skill_task":{if(!n.skill){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Node "${t}" is skill_task but has no skill defined`)}if(!U.SKILL_NAME_RE.test(n.skill)){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Node "${t}" has invalid skill name "${n.skill}" (must match ^[a-z0-9][a-z0-9-]*$)`)}const u={...r.state,state:r.state};const h=(n.args??[]).map(O=>b(String(O),u));const y=n.stdin!==void 0?b(String(n.stdin),u):void 0;const A=(0,U.normalizeCwd)(n.cwd?b(n.cwd,u):void 0,this.projectRoot);if(!A.ok){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Node "${t}": ${A.error}`)}const v=(0,U.normalizeCwd)(void 0,this.projectRoot);if(!v.ok){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Node "${t}": ${v.error}`)}const E=v.resolved;const G=n.timeout??exports.SKILL_TASK_DEFAULT_TIMEOUT;if(o?.dryRun){console.log(`[dry-run] Would execute: crewx skill ${n.skill} ${h.join(" ")}`.trim());break}console.log(`Executing skill: ${n.skill} ${h.join(" ")}`.trim());const j=Se({...process.env,CREWX_WORKSPACE:E,CREWX_WORKFLOW_EXEC_ID:r.id,CREWX_WORKFLOW_NODE_ID:t,CREWX_WORKFLOW_ID:r.workflow_id},E);const P=[...j.args,"skill",n.skill,...h];const T=(0,N.resolveWindowsSpawnProgram)({command:j.command,env:j.env,cwd:A.resolved,allowShellFallback:true,packageName:"crewx"});const L=(0,N.materializeWindowsSpawnProgram)(T,P);const $=await ue(L.command,L.argv,{cwd:A.resolved,env:j.env,timeout:G,shell:L.shell??false,windowsHide:L.windowsHide,stdin:y,cancellation:this.processCancellation(e)});I($);if($.cancelled){throw(0,d.createWorkflowFailureError)("CANCELLED",`skill_task "${t}" was cancelled`)}if($.timedOut)throw new Error(`skill_task "${t}" timed out after ${G}ms`);if($.error)throw $.error;if($.status!==0){throw new Error(`skill_task "${t}" failed (exit ${$.status}): ${$.stderr.trim()}`)}await Le(n,t,$.stdout,p);if(n.output){console.log(`Result stored in state.${n.output}`)}break}case"shell_task":{const u=process.env.CREWX_WORKFLOW_SHELL==="1";const h=i.metadata?.shell_task_allowed===true;if(!u||!h){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID","shell_task is disabled. Set CREWX_WORKFLOW_SHELL=1 and metadata.shell_task_allowed=true to enable.")}const y=(0,U.validateShellCommand)(n.command);if(!y.ok){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Node "${t}": ${y.error}`)}const A=(0,U.validateEnvKeys)(n.env);if(!A.ok){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Node "${t}": ${A.error}`)}const v={...r.state,state:r.state};const E=n.command.map(z=>b(z,v));const G=(0,U.validateShellCommand)(E);if(!G.ok){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Node "${t}": ${G.error}`)}const j=n.env?Object.fromEntries(Object.entries(n.env).map(([z,te])=>[z,b(String(te),v)])):void 0;const P=(0,U.normalizeCwd)(n.cwd?b(n.cwd,v):void 0,this.projectRoot);if(!P.ok){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Node "${t}": ${P.error}`)}const T=n.timeout??exports.SKILL_TASK_DEFAULT_TIMEOUT;if(o?.dryRun){console.log(`[dry-run] Would execute: ${E.join(" ")}`);break}console.log(`Executing shell_task: ${E[0]} (${E.length-1} args)`);const L=(0,U.buildShellEnv)(j,process.env);L.CREWX_WORKFLOW_EXEC_ID=r.id;L.CREWX_WORKFLOW_NODE_ID=t;L.CREWX_WORKFLOW_ID=r.workflow_id;const $=n.stdin!==void 0?b(String(n.stdin),v):void 0;const O=await ue(E[0],E.slice(1),{cwd:P.resolved,env:L,timeout:T,shell:false,stdin:$,cancellation:this.processCancellation(e)});I(O);if(O.cancelled){throw(0,d.createWorkflowFailureError)("CANCELLED",`shell_task "${t}" was cancelled`)}if(O.timedOut)throw new Error(`shell_task "${t}" timed out after ${T}ms`);if(O.error)throw O.error;if(O.status!==0){throw new Error(`shell_task "${t}" failed (exit ${O.status}): ${O.stderr.trim()}`)}await Le(n,t,O.stdout,p);if(n.output){console.log(`Result stored in state.${n.output}`)}break}case"branch":{if(!n.condition){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Branch node "${t}" has no condition`)}const u=String(de(String(n.condition),r.state));const h=n.branches;const y=h?.[u]??n.default;if(!y){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Branch "${t}": no matching branch for "${u}" and no default`)}console.log(`Branch "${t}": condition="${n.condition}" \u2192 ${u} \u2192 ${y}`);l.push(t);f=y;break}case"parallel":{const u=n.branches;if(u&&Array.isArray(u)){console.log(`Parallel "${t}": branches = [${u.join(", ")}]`);console.log(` Enter each branch sequentially with: workflow run node <exec-id> <branch-node>`)}break}case"join":{console.log(`Join "${t}": completed_nodes = [${r.completed_nodes.join(", ")}]`);break}case"end":{m="completed";console.log(`End "${t}": workflow execution completed`);break}case"error":{m="failed";S="UNKNOWN";const u={...r.state,state:r.state};_=n.message?b(n.message,u):`Workflow failed at error node "${t}"`;console.log(`Error "${t}": workflow execution failed \u2014 ${_}`);break}case"expression":{if(!n.set||typeof n.set!=="object"){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Expression node "${t}" requires "set" field`)}for(const[u,h]of Object.entries(n.set)){const y=ke(String(h),r.state);p[u]=String(y);console.log(` ${u} = ${y}`)}console.log(`Expression "${t}": set ${Object.keys(n.set).join(", ")}`);if(n.next){l.push(t);f=n.next}break}case"approval":{console.log(`Approval "${t}": manual intervention required (not auto-executed)`);break}default:console.log(`Unknown node type "${n.type}" for node "${t}"`)}if(n.fail_when&&!o?.dryRun&&(n.type==="agent_task"||n.type==="skill_task"||n.type==="shell_task")){const u=de(String(n.fail_when),{...r.state,...p});if(u===true||u==="true"){const h=n.output?p[n.output]:void 0;if(h&&typeof h==="object"&&!Array.isArray(h)){const y=h.reason;if(typeof y==="string"){const A=(0,N.redactTechnicalMessage)(y);const v=A?.split(/\r\n|\r|\n/u).find(E=>E.trim());if(v)Z=v.trim().slice(0,180)}}throw new Error(`Node "${t}" judged failed by fail_when: ${n.fail_when}`)}}}catch(u){const h=J(u);const y=(0,d.classifyWorkflowFailure)({errorCode:(0,d.getWorkflowFailureCode)(u),process:q});const A=(0,d.buildNodeFailure)({failedNode:t,attempt:g,causeCode:y,exitCode:y==="NODE_EXIT_NONZERO"&&typeof F==="number"?F:null,signal:y==="INTERRUPTED"?H:null,errorOrigin:X,timedOut:q?.timedOut??false});const v=Q(Z??A.causeMessage);await this.atomicUpdateAsync(e,E=>{if(E.status==="cancelled")return;Object.assign(E.state,p);delete E.manual_resume;E.current_node=t;V(E,A,Z??h);E.audit=E.audit??[];E.audit.push(v)});throw u}const M=S?(0,d.buildNodeFailure)({failedNode:t,attempt:g,causeCode:S,exitCode:null,signal:null,errorOrigin:null}):null;const we=Q(M?.causeMessage);return this.atomicUpdateAsync(e,u=>{if(u.status==="cancelled")return;Object.assign(u.state,p);delete u.manual_resume;for(const h of l){if(!u.completed_nodes.includes(h)){u.completed_nodes.push(h)}}if(c&&c!==t&&!u.completed_nodes.includes(c)){u.completed_nodes.push(c)}u.current_node=f;if(M){V(u,M,_??M.causeMessage)}else{delete u.error;delete u.last_failure;this.clearNodeAttempt(u,t);if(m){u.status=m}else if(u.status!=="completed"){u.status="running"}}if(C){u.tasks=u.tasks??{};u.tasks[t]=C}u.audit=u.audit??[];u.audit.push(we)})}async claimAutoRunLease(e,t,o){let r=false;const s=await this.atomicUpdateAsync(e,i=>{if(pe(i.status))return;const n=i.auto_run;const c=n?.pid===process.pid&&typeof n.token==="string";if(n&&!c&&De(n.pid)){throw new Error(`Workflow run "${e}" is already owned by another runner process`)}const p=o??(c?n.token:(0,N.generateId)("wfr"));if(!pe(i.status)){i.status="running"}if(!i.current_node)i.current_node=t;i.auto_run={pid:process.pid,token:p,node_id:t,started_at:c&&n.token===p?n.started_at:new Date().toISOString()};delete i.manual_resume;r=true});return r?s.auto_run?.token??null:null}async releaseAutoRunLease(e,t){await this.atomicUpdateAsync(e,o=>{if(o.auto_run?.token===t)delete o.auto_run})}async failAutoConfiguration(e,t,o){return this.atomicUpdateAsync(e,r=>{if(r.status==="cancelled")return;const s=(0,d.buildNodeFailure)({failedNode:t,attempt:(0,d.readNodeAttempt)(r,t),causeCode:"CONFIG_INVALID",exitCode:null,signal:null,errorOrigin:null});V(r,s,o);Y(r,s,"config","auto")})}async reserveAutoRetry(e,t,o,r,s){let i=false;const n=await this.atomicUpdateAsync(e,c=>{if(c.status==="cancelled")return;if(c.status!=="failed")return;if(c.auto_run?.token!==o)return;const p=(0,ce.decideRetry)({policy:r,run:c,nodeId:t});if(!p.retry||p.attempt===void 0)return;const l=new Date().toISOString();c.status="retrying";delete c.error;delete c.manual_resume;c.audit=c.audit??[];c.audit.push({exec_id:c.id,node_id:t,type:"retry",summary:`auto retry ${p.attempt+1}/${r.max+1} in ${s}ms`,started_at:l,ended_at:l,duration_ms:0,exit_code:null,trigger:"auto"});i=true});return{run:n,reserved:i}}async waitForRetry(e,t,o){const r=()=>{const c=this.loadRun(e);if(!c)throw new Error(`Execution not found: ${e}`);if(c.status==="cancelled"){return{run:c,outcome:"cancelled",reason:"run cancelled during retry delay"}}if(c.auto_run?.token!==t){return{run:c,outcome:"paused",reason:"auto-run lease lost during retry delay"}}if(c.status!=="retrying"){return{run:c,outcome:"paused",reason:"run state changed during retry delay"}}return null};let s=Math.max(0,o);while(s>0){const c=Math.min(s,Xe);await this.sleep(c);s-=c;const p=r();if(p)return p}const i=r();if(i)return i;const n=this.loadRun(e);if(!n)throw new Error(`Execution not found: ${e}`);return{run:n,outcome:"ready"}}async runNodeWithRetry(e,t,o,r){let s=o;while(true){try{const i=await this.executeNode(e,t,{trigger:"auto"});if(i.status==="cancelled"){return{run:i,runnerToken:s,outcome:"cancelled",reason:i.error}}return{run:i,runnerToken:s,outcome:"success"}}catch(i){const n=this.loadRun(e);if(!n)throw new Error(`Execution not found: ${e}`);if(n.status==="cancelled"){return{run:n,runnerToken:s,outcome:"cancelled",reason:n.error}}if(n.status!=="failed"||!r){return{run:n,runnerToken:s,outcome:n.status==="failed"?"failed":"paused",reason:J(i)}}const c=(0,ce.decideRetry)({policy:r,run:n,nodeId:t});if(!c.retry||c.attempt===void 0){return{run:n,runnerToken:s,outcome:"failed",reason:J(i)}}const p=(0,ce.computeRetryDelayMs)(r,c.attempt);const l=await this.reserveAutoRetry(e,t,s,r,p);if(!l.reserved){const _=l.run;if(_.status==="cancelled"){return{run:_,runnerToken:s,outcome:"cancelled",reason:_.error}}return{run:_,runnerToken:s,outcome:_.status==="failed"?"failed":"paused",reason:J(i)}}const f=await this.waitForRetry(e,s,p);if(f.outcome!=="ready"){return{run:f.run,runnerToken:s,outcome:f.outcome,reason:f.reason}}const m=await this.claimAutoRunLease(e,t,s);if(!m){const _=this.loadRun(e);if(!_)throw new Error(`Execution not found: ${e}`);if(_.status==="cancelled"){return{run:_,runnerToken:s,outcome:"cancelled",reason:_.error}}return{run:_,runnerToken:s,outcome:"paused",reason:"run stopped before retry lease could be reclaimed"}}s=m}}}async runAuto(e){const t=`${this.resolvedDir}::${e}`;if(Re.has(t)){const r=this.loadRun(e);if(!r)throw new Error(`Execution not found: ${e}`);console.log(`runAuto "${e}": already in-flight \u2014 skipping duplicate auto-run (single-flight guard)`);return{run:r,outcome:"paused",reason:"execution already in-flight (single-flight guard)",executed:[]}}Re.add(t);let o;try{const r=this.loadRun(e);if(!r)throw new Error(`Execution not found: ${e}`);if(r.auto_run?.pid===process.pid){o=r.auto_run.token}const s=this.loadWorkflowYaml(r.workflow_file);if(!s){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Failed to load workflow file: ${r.workflow_file}`)}const i=s.workflows[r.workflow_id];if(!i){throw(0,d.createWorkflowFailureError)("CONFIG_INVALID",`Workflow "${r.workflow_id}" not found in ${r.workflow_file}`)}const n=[];if(r.status==="completed"||r.status==="failed"||r.status==="cancelled"){return{run:r,outcome:r.status==="completed"?"completed":r.status==="cancelled"?"cancelled":"failed",reason:`run already ${r.status}`,executed:n}}const c=Object.keys(i.nodes??{})[0];const p=typeof i.max_iterations==="number"&&i.max_iterations>0?i.max_iterations:30;let l=r;while(true){l=this.loadRun(e);if(l.status==="completed")return{run:l,outcome:"completed",executed:n};if(l.status==="failed")return{run:l,outcome:"failed",reason:l.error,executed:n};if(l.status==="cancelled")return{run:l,outcome:"cancelled",reason:l.error,executed:n};const f=l.current_node||c;if(!f){l=await this.atomicUpdateAsync(e,w=>{const k=(0,d.buildNodeFailure)({failedNode:w.current_node||"",attempt:(0,d.readNodeAttempt)(w,w.current_node||""),causeCode:"CONFIG_INVALID",exitCode:null,signal:null,errorOrigin:null});V(w,k,"workflow has no nodes");Y(w,k,"config","auto")});return{run:l,outcome:"failed",reason:l.error,executed:n}}const m=i.nodes[f];if(!m){l=await this.atomicUpdateAsync(e,w=>{w.current_node=f;const k=(0,d.buildNodeFailure)({failedNode:f,attempt:(0,d.readNodeAttempt)(w,f),causeCode:"CONFIG_INVALID",exitCode:null,signal:null,errorOrigin:null});V(w,k,`node "${f}" not found in workflow`);Y(w,k,"config","auto")});return{run:l,outcome:"failed",reason:l.error,executed:n}}const _=(0,ce.parseRetryPolicy)(m);if(!_.ok){l=await this.failAutoConfiguration(e,f,_.reason);if(l.status==="cancelled"){return{run:l,outcome:"cancelled",reason:l.error,executed:n}}return{run:l,outcome:"failed",reason:l.error,executed:n}}if(m.type==="approval"){if(l.current_node!==f){l=await this.atomicUpdateAsync(e,w=>{w.current_node=f})}return{run:l,outcome:"paused",reason:`approval node "${f}" requires manual decision`,executed:n}}if(m.type==="parallel"){if(l.current_node!==f){l=await this.atomicUpdateAsync(e,w=>{w.current_node=f})}return{run:l,outcome:"paused",reason:`parallel auto-run not implemented (node "${f}")`,executed:n}}if(!$e.has(m.type)){if(l.current_node!==f){l=await this.atomicUpdateAsync(e,w=>{w.current_node=f})}return{run:l,outcome:"paused",reason:`unknown node type "${m.type}" (node "${f}")`,executed:n}}const S=await this.claimAutoRunLease(e,f,o);if(!S){l=this.loadRun(e);return{run:l,outcome:l.status==="cancelled"?"cancelled":"paused",reason:l.status==="cancelled"?l.error:"run stopped before auto-run lease could be claimed",executed:n}}o=S;l=await this.atomicUpdateAsync(e,w=>{if(w.status==="cancelled")return;const k=lt(w);k.visitCounts[f]=(k.visitCounts[f]??0)+1;k.iterations+=1;w.state["__engine"]=k;if(k.visitCounts[f]>p||k.iterations>p){const F=(0,d.buildNodeFailure)({failedNode:f,attempt:(0,d.readNodeAttempt)(w,f),causeCode:"CONFIG_INVALID",exitCode:null,signal:null,errorOrigin:null});V(w,F,`auto runner exceeded max_iterations (${p}); possible loop at "${f}"`);Y(w,F,"config","auto")}});if(l.status==="cancelled"){return{run:l,outcome:"cancelled",reason:l.error,executed:n}}if(l.status==="failed"){return{run:l,outcome:"failed",reason:l.error,executed:n}}const C=await this.runNodeWithRetry(e,f,o,_.policy);l=C.run;o=C.runnerToken;if(C.outcome!=="success"){return{run:l,outcome:C.outcome,reason:C.reason,executed:n}}n.push(f);if(l.status==="completed")return{run:l,outcome:"completed",executed:n};if(l.status==="failed")return{run:l,outcome:"failed",reason:l.error,executed:n};if(l.status==="cancelled")return{run:l,outcome:"cancelled",reason:l.error,executed:n};if(!Je.has(m.type)){const w=m.next;if(!w){return{run:l,outcome:"paused",reason:`node "${f}" has no "next"; stopping`,executed:n}}l=this.moveNode(e,w)}}}finally{if(o){try{await this.releaseAutoRunLease(e,o)}catch{}}Re.delete(t)}}cancelRun(e,t=Ke.USER_CANCEL_REASON){const o=this.atomicUpdate(e,r=>{if(r.status==="completed"||r.status==="failed"||r.status==="cancelled"){return}const s=new Date;const i=r.auto_run?.node_id||r.current_node||"";const n=(0,d.buildNodeFailure)({failedNode:i,attempt:(0,d.readNodeAttempt)(r,i),causeCode:"CANCELLED",exitCode:null,signal:null,errorOrigin:null});V(r,n,t);r.status="cancelled";delete r.auto_run;delete r.manual_resume;Y(r,n,"cancel","manual",s.toISOString())});if(o.status==="cancelled"){ct(this.resolvedDir,e)}return o}decideApproval(e,t,o){const r=new Date().toISOString();return this.atomicUpdate(e,s=>{if(pe(s.status)){throw new Error(`Workflow run "${e}" is ${s.status}; no approval decision can be recorded`)}const i=s.current_node;const n=i?o.spec.nodes[i]:void 0;if(!i||n?.type!=="approval"){throw new Error(`Workflow run "${e}" is not waiting at an approval node (current: "${i||"(none)"}")`)}const c=t==="approve"?n.on_approve:n.on_reject;if(typeof c!=="string"||!c.trim()){throw new Error(`Approval node "${i}" has no ${t==="approve"?"on_approve":"on_reject"} target`)}if(!s.completed_nodes.includes(i))s.completed_nodes.push(i);s.current_node=c;if(t==="reject")s.state["plan_feedback"]=(o.reason??"").trim();s.audit=s.audit??[];s.audit.push({exec_id:s.id,node_id:i,type:"approval",summary:`${t} \u2192 ${c}`,started_at:r,ended_at:r,duration_ms:0,exit_code:null,trigger:"manual",decision:t,decided_at:r,actor:o.actor})})}reset(e){return this.atomicUpdate(e,t=>{t.state=JSON.parse(JSON.stringify(t.initial_state));t.current_node="";t.completed_nodes=[];t.status="pending";delete t.error;delete t.last_failure;delete t.node_attempts;delete t.auto_run;delete t.manual_resume})}retry(e,t={}){const o=this.loadRun(e);if(!o)throw new Error(`Execution not found: ${e}`);const r=this.loadWorkflowYaml(o.workflow_file);if(!r)throw new Error(`Failed to load workflow file: ${o.workflow_file}`);const s=r.workflows[o.workflow_id];if(!s)throw new Error(`Workflow "${o.workflow_id}" not found`);if(o.status!=="failed"){throw new Error(`Run "${e}" is not failed (status=${o.status??"running"}); only failed runs can be retried`)}if(!Ue(o,s)){throw new Error(`Run "${e}" failed at a non-retryable node "${o.current_node}" (error node); use reset to restart`)}return this.atomicUpdate(e,i=>{if(i.status!=="failed"){throw new Error(`Run "${e}" is not failed (status=${i.status??"pending"}); only failed runs can be retried`)}const n=i.current_node;if(n&&i.last_failure){const p=i.last_failure.attempt;const l=i.node_attempts?.[n];if(typeof p==="number"&&Number.isInteger(p)&&p>0&&(typeof l!=="number"||l<p)){i.node_attempts=i.node_attempts??{};i.node_attempts[n]=p}}i.status="retrying";delete i.error;delete i.manual_resume;i.retry_count=(i.retry_count??0)+1;i.audit=i.audit??[];const c=new Date().toISOString();if(t.autoRun===false){delete i.auto_run;i.manual_resume={node_id:n,requested_at:c}}else{i.auto_run={pid:process.pid,token:(0,N.generateId)("wfr"),node_id:n,started_at:c}}i.audit.push({exec_id:i.id,node_id:n,type:"retry",summary:`manual retry #${i.retry_count}`,started_at:c,ended_at:c,duration_ms:0,exit_code:null,trigger:"manual"})})}listRunIds(){try{return R.readdirSync(this.resolvedDir,{withFileTypes:true}).filter(e=>e.isFile()&&!e.name.startsWith(".")&&e.name.endsWith(".json")).map(e=>e.name.slice(0,-".json".length))}catch(e){if(e.code==="ENOENT")return[];throw e}}listRuns(e){this.ensureRunsDir();const t=R.readdirSync(this.resolvedDir).filter(r=>r.endsWith(".json"));const o=[];for(const r of t){try{const s=r.slice(0,-".json".length);const i=this.loadRun(s);if(!i)continue;if(e?.workflowId&&i.workflow_id!==e.workflowId)continue;o.push(i)}catch{}}o.sort((r,s)=>new Date(s.created_at).getTime()-new Date(r.created_at).getTime());return o}}exports.RunManager=dt;
@@ -0,0 +1,30 @@
1
+ import type { RunExecution, WorkflowSpec } from './types.js';
2
+ export type RunStepStatus = 'not_started' | 'running' | 'waiting_approval' | 'approved' | 'rejected' | 'completed' | 'failed' | 'stopped';
3
+ export interface RunStepProjection {
4
+ nodeId: string;
5
+ label: string;
6
+ type: string | null;
7
+ status: RunStepStatus;
8
+ isApproval: boolean;
9
+ startedAt: string | null;
10
+ endedAt: string | null;
11
+ durationMs: number | null;
12
+ exitCode: number | null;
13
+ retryCount: number;
14
+ taskIds: string[];
15
+ }
16
+ export type RunStepTaskLookup = (workflowRunId: string, nodeId: string) => readonly string[];
17
+ export interface RunProgressSummary {
18
+ done: number;
19
+ total: number | null;
20
+ current: {
21
+ nodeId: string;
22
+ label: string;
23
+ } | null;
24
+ waitingApproval: boolean;
25
+ }
26
+ export declare function projectRunSteps(definition: WorkflowSpec | null | undefined, run: RunExecution, options?: {
27
+ findTaskIds?: RunStepTaskLookup;
28
+ }): RunStepProjection[];
29
+ export declare function summarizeRunProgress(definition: WorkflowSpec | null | undefined, run: RunExecution): RunProgressSummary;
30
+
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.projectRunSteps=N;exports.summarizeRunProgress=A;const j=require("./cancel-reason.js");const p="approval";const m="retry";function v(e){return e.status??(e.auto_run?"running":"pending")}function g(e){return(0,j.isUserCancelledRun)(e)?"stopped":"failed"}function y(e){return e==="end"||e==="error"}function R(e,t){for(let r=e.length-1;r>=0;r-=1){const n=e[r];if(n.node_id===t&&n.type===p&&n.decision)return n}return void 0}function h(e){let t;for(const r of e){const n=Date.parse(r);if(Number.isFinite(n)&&(!t||n<t.ms))t={value:r,ms:n}}return t?.value??null}function D(e){let t;for(const r of e){const n=Date.parse(r);if(Number.isFinite(n)&&(!t||n>t.ms))t={value:r,ms:n}}return t?.value??null}function O(e,t,r,n){if(n)return n.decision==="reject"?"rejected":"approved";const o=v(r);if(r.current_node===e){if(o==="cancelled")return g(r);if(o==="failed")return"failed";if(o==="completed")return"completed";return"waiting_approval"}if(!r.completed_nodes.includes(e))return"not_started";const c=a=>typeof a==="string"&&(r.completed_nodes.includes(a)||r.current_node===a);if(c(t?.on_approve))return"approved";if(c(t?.on_reject))return"rejected";return"completed"}function P(e,t,r){const n=v(t);const o=t.auto_run?.node_id||t.current_node;if(n==="cancelled"&&o===e&&!t.completed_nodes.includes(e)){return g(t)}if(t.completed_nodes.includes(e))return"completed";if(t.current_node===e){if(n==="running"||n==="retrying")return"running";if(n==="failed")return"failed";if(n==="completed")return"completed";return"not_started"}const c=r.at(-1);if(c)return c.error?"failed":"completed";return"not_started"}function T(e,t,r,n,o){const c=n.filter(d=>d.node_id===e&&d.type!==m&&d.type!==p);const a=R(n,e);const s=t?.type==="approval"||a!==void 0;const i=t?.type??(s?p:null);const l=s?a?.decided_at??null:h(c.map(d=>d.started_at));const u=s?a?.decided_at??null:D(c.map(d=>d.ended_at));const f=l?Date.parse(l):NaN;const _=u?Date.parse(u):NaN;const S=Number.isFinite(f)&&Number.isFinite(_)?Math.max(0,_-f):null;return{nodeId:e,label:t?.label??e,type:i,status:s?O(e,t,r,a):P(e,r,c),isApproval:s,startedAt:l,endedAt:u,durationMs:S,exitCode:s?null:c.at(-1)?.exit_code??null,retryCount:n.filter(d=>d.node_id===e&&d.type===m).length,taskIds:o?[...o(r.id,e)]:[]}}function b(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function w(e,t){const r=new Set;const n=[...e.completed_nodes,e.current_node,e.auto_run?.node_id,...(e.audit??[]).map(o=>o.node_id)];for(const o of n){if(o&&!b(t,o))r.add(o)}return[...r]}function N(e,t,r={}){const n=e?.nodes??{};const o=t.audit??[];const c=Object.keys(n);const a=[...c,...w(t,n)];return a.map(s=>T(s,b(n,s)?n[s]:void 0,t,o,r.findTaskIds))}function A(e,t){const r=N(e,t);const n=s=>s==="completed"||s==="approved"||s==="rejected";let o;let c;if(e){const s=Object.keys(e.nodes).filter(l=>!y(e.nodes[l]?.type));const i=new Map(r.map(l=>[l.nodeId,l]));o=s.filter(l=>{const u=i.get(l);return u!==void 0&&n(u.status)}).length;c=s.length}else{const s=r.filter(i=>!y(i.type));o=s.filter(i=>n(i.status)).length;c=null}const a=t.current_node?r.find(s=>s.nodeId===t.current_node):void 0;return{done:o,total:c,current:a?{nodeId:a.nodeId,label:a.label}:null,waitingApproval:r.some(s=>s.status==="waiting_approval")}}
@@ -41,6 +41,7 @@ export interface WorkflowNode {
41
41
  default?: string;
42
42
  join?: string;
43
43
  on_failure?: 'fail_fast' | 'continue' | 'ignore';
44
+ label?: string;
44
45
  description?: string;
45
46
  assignee?: string;
46
47
  timeout?: number;
@@ -88,6 +89,9 @@ export interface AuditEntry {
88
89
  exit_code: number | null;
89
90
  error?: string;
90
91
  trigger: 'manual' | 'auto';
92
+ decision?: 'approve' | 'reject';
93
+ decided_at?: string;
94
+ actor?: string;
91
95
  }
92
96
  export interface WorkflowAutoRunLease {
93
97
  pid: number;
@@ -170,6 +170,10 @@
170
170
  "type": "string",
171
171
  "description": "error: failure message recorded in run.error. Supports {{template}} interpolation."
172
172
  },
173
+ "label": {
174
+ "type": "string",
175
+ "description": "Short human-facing step name shown in run views. Falls back to the node id."
176
+ },
173
177
  "on_approve": {
174
178
  "type": "string"
175
179
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crewx/workflow",
3
- "version": "0.3.22-rc.220",
3
+ "version": "0.3.22-rc.221",
4
4
  "description": "Workflow engine for CrewX - list, show, validate workflows with Mermaid visualization",
5
5
  "type": "commonjs",
6
6
  "main": "dist/src/engine.js",
@@ -67,9 +67,9 @@
67
67
  "dependencies": {
68
68
  "@hyperjump/json-schema": "^1.9.4",
69
69
  "js-yaml": "^4.1.0",
70
- "@crewx/knowledge-core": "0.1.17-rc.184",
71
- "@crewx/shared": "0.0.6-rc.89",
72
- "@crewx/sdk": "0.9.0-rc.174"
70
+ "@crewx/sdk": "0.9.0-rc.175",
71
+ "@crewx/knowledge-core": "0.1.17-rc.185",
72
+ "@crewx/shared": "0.0.6-rc.90"
73
73
  },
74
74
  "devDependencies": {
75
75
  "@types/js-yaml": "^4.0.9",
@@ -170,6 +170,10 @@
170
170
  "type": "string",
171
171
  "description": "error: failure message recorded in run.error. Supports {{template}} interpolation."
172
172
  },
173
+ "label": {
174
+ "type": "string",
175
+ "description": "Short human-facing step name shown in run views. Falls back to the node id."
176
+ },
173
177
  "on_approve": {
174
178
  "type": "string"
175
179
  },