@crewx/workflow 0.3.22-rc.2 → 0.3.22-rc.200

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/SKILL.md +255 -11
  2. package/dist/cli.d.ts +1 -1
  3. package/dist/cli.js +23 -945
  4. package/dist/src/card-state.d.ts +20 -0
  5. package/dist/src/card-state.js +1 -0
  6. package/dist/src/engine.d.ts +10 -1
  7. package/dist/src/engine.js +1 -300
  8. package/dist/src/failure.d.ts +41 -0
  9. package/dist/src/failure.js +1 -0
  10. package/dist/src/file-lock.d.ts +1 -1
  11. package/dist/src/file-lock.js +1 -97
  12. package/dist/src/list-view.d.ts +22 -0
  13. package/dist/src/list-view.js +3 -0
  14. package/dist/src/mermaid.d.ts +1 -1
  15. package/dist/src/mermaid.js +2 -117
  16. package/dist/src/parent-task-sync.d.ts +139 -0
  17. package/dist/src/parent-task-sync.js +1 -0
  18. package/dist/src/retry-policy.d.ts +38 -0
  19. package/dist/src/retry-policy.js +1 -0
  20. package/dist/src/run-manager.d.ts +44 -3
  21. package/dist/src/run-manager.js +4 -764
  22. package/dist/src/types.d.ts +65 -4
  23. package/dist/src/types.js +1 -3
  24. package/dist/src/utils/output-format.d.ts +13 -1
  25. package/dist/src/utils/output-format.js +23 -119
  26. package/dist/src/utils/shell-security.d.ts +18 -0
  27. package/dist/src/utils/shell-security.js +1 -0
  28. package/dist/src/workflow-persistence.d.ts +48 -0
  29. package/dist/src/workflow-persistence.js +1 -0
  30. package/dist/workflow-schema.json +66 -4
  31. package/package.json +35 -4
  32. package/workflow-schema.json +66 -4
  33. package/dist/cli.d.ts.map +0 -1
  34. package/dist/cli.js.map +0 -1
  35. package/dist/src/engine.d.ts.map +0 -1
  36. package/dist/src/engine.js.map +0 -1
  37. package/dist/src/file-lock.d.ts.map +0 -1
  38. package/dist/src/file-lock.js.map +0 -1
  39. package/dist/src/mermaid.d.ts.map +0 -1
  40. package/dist/src/mermaid.js.map +0 -1
  41. package/dist/src/run-manager.d.ts.map +0 -1
  42. package/dist/src/run-manager.js.map +0 -1
  43. package/dist/src/types.d.ts.map +0 -1
  44. package/dist/src/types.js.map +0 -1
  45. package/dist/src/utils/output-format.d.ts.map +0 -1
  46. package/dist/src/utils/output-format.js.map +0 -1
@@ -2,29 +2,37 @@ export interface WorkflowMeta {
2
2
  name: string;
3
3
  description?: string;
4
4
  version?: string;
5
+ shell_task_allowed?: boolean;
5
6
  }
6
7
  export interface RetryConfig {
7
8
  max: number;
8
9
  delay: 'exponential' | 'fixed';
9
10
  max_delay?: number;
10
- on?: string[];
11
+ on?: WorkflowFailureCode[];
11
12
  }
12
13
  export interface OnError {
13
14
  handler?: string;
14
15
  fallback_node?: string;
15
16
  }
16
- export type NodeType = 'agent_task' | 'parallel' | 'join' | 'branch' | 'approval' | 'expression' | 'end';
17
+ export type NodeType = 'agent_task' | 'skill_task' | 'shell_task' | 'parallel' | 'join' | 'branch' | 'approval' | 'expression' | 'end' | 'error';
17
18
  export type JsonSchema = Record<string, unknown>;
18
19
  export interface WorkflowNode {
19
20
  type: NodeType;
20
21
  agent?: string;
21
22
  mode?: 'query' | 'execute';
22
23
  input?: string;
24
+ skill?: string;
25
+ args?: string[];
26
+ command?: string[];
27
+ cwd?: string;
28
+ env?: Record<string, string>;
29
+ stdin?: string;
23
30
  output?: string;
24
31
  output_format?: 'json';
25
32
  output_schema?: JsonSchema;
26
33
  output_retry?: number;
27
34
  output_strict?: boolean;
35
+ fail_when?: string;
28
36
  next?: string;
29
37
  hooks?: string[];
30
38
  retry?: RetryConfig;
@@ -42,6 +50,7 @@ export interface WorkflowNode {
42
50
  on_reject?: string;
43
51
  on_error?: OnError;
44
52
  set?: Record<string, string>;
53
+ message?: string;
45
54
  }
46
55
  export interface WorkflowSpec {
47
56
  metadata?: WorkflowMeta;
@@ -68,6 +77,45 @@ export interface ValidationResult {
68
77
  valid: boolean;
69
78
  errors: ValidationError[];
70
79
  }
80
+ export interface AuditEntry {
81
+ exec_id: string;
82
+ node_id: string;
83
+ type: NodeType | 'retry' | string;
84
+ summary: string;
85
+ started_at: string;
86
+ ended_at: string;
87
+ duration_ms: number;
88
+ exit_code: number | null;
89
+ error?: string;
90
+ trigger: 'manual' | 'auto';
91
+ }
92
+ export interface WorkflowAutoRunLease {
93
+ pid: number;
94
+ token: string;
95
+ node_id: string;
96
+ started_at: string;
97
+ }
98
+ export interface WorkflowManualResume {
99
+ node_id: string;
100
+ requested_at: string;
101
+ }
102
+ export type WorkflowFailureCode = 'CLI_UNRESOLVABLE' | 'SPAWN_FAILED' | 'NODE_TIMEOUT' | 'NODE_EXIT_NONZERO' | 'OUTPUT_INVALID' | 'RATE_LIMITED' | 'CONFIG_INVALID' | 'INTERRUPTED' | 'CANCELLED' | 'UNKNOWN';
103
+ export interface RetryPolicy {
104
+ max: number;
105
+ delay: 'exponential' | 'fixed';
106
+ max_delay: number;
107
+ on: WorkflowFailureCode[];
108
+ }
109
+ export declare const NEVER_RETRYABLE: readonly WorkflowFailureCode[];
110
+ export interface WorkflowNodeFailure {
111
+ failedNode: string;
112
+ attempt: number;
113
+ causeCode: WorkflowFailureCode;
114
+ causeMessage: string;
115
+ exitCode: number | null;
116
+ evidencePreview: string | null;
117
+ occurredAt: string;
118
+ }
71
119
  export interface RunExecution {
72
120
  id: string;
73
121
  workflow_id: string;
@@ -78,7 +126,20 @@ export interface RunExecution {
78
126
  completed_nodes: string[];
79
127
  state: Record<string, unknown>;
80
128
  initial_state: Record<string, unknown>;
81
- status?: 'running' | 'completed' | 'failed';
129
+ status?: 'pending' | 'running' | 'retrying' | 'completed' | 'failed' | 'cancelled';
82
130
  tasks?: Record<string, string>;
131
+ error?: string;
132
+ last_failure?: WorkflowNodeFailure;
133
+ audit?: AuditEntry[];
134
+ retry_count?: number;
135
+ node_attempts?: Record<string, number>;
136
+ auto_run?: WorkflowAutoRunLease;
137
+ manual_resume?: WorkflowManualResume;
138
+ }
139
+ export interface AutoRunResult {
140
+ run: RunExecution;
141
+ outcome: 'completed' | 'failed' | 'cancelled' | 'paused';
142
+ reason?: string;
143
+ executed: string[];
83
144
  }
84
- //# sourceMappingURL=types.d.ts.map
145
+
package/dist/src/types.js CHANGED
@@ -1,3 +1 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- //# sourceMappingURL=types.js.map
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.NEVER_RETRYABLE=void 0;exports.NEVER_RETRYABLE=["CANCELLED","CONFIG_INVALID"];
@@ -1,6 +1,18 @@
1
1
  import type { JsonSchema } from '../types';
2
+ export interface WorkflowPreambleContext {
3
+ workflowId: string;
4
+ nodeId: string;
5
+ execId: string;
6
+ }
7
+ export declare const WORKFLOW_SIGNAL_KEY = "workflow_signal";
8
+ export interface WorkflowSignal {
9
+ signal: 'fail';
10
+ reason?: string;
11
+ }
12
+ export declare function buildWorkflowPreamble(ctx: WorkflowPreambleContext): string;
13
+ export declare function detectWorkflowSignal(output: string): WorkflowSignal | null;
2
14
  export declare function buildJsonOutputSuffix(schema?: JsonSchema, strict?: boolean): string;
3
15
  export declare function extractJson(text: string): string;
4
16
  export declare function validateJsonSchema(value: unknown, schema: JsonSchema): Promise<void>;
5
17
  export declare function toOpenAIStrictSchema(schema: JsonSchema): JsonSchema;
6
- //# sourceMappingURL=output-format.d.ts.map
18
+
@@ -1,125 +1,29 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.buildJsonOutputSuffix = buildJsonOutputSuffix;
37
- exports.extractJson = extractJson;
38
- exports.validateJsonSchema = validateJsonSchema;
39
- exports.toOpenAIStrictSchema = toOpenAIStrictSchema;
40
- const node_crypto_1 = require("node:crypto");
41
- function buildJsonOutputSuffix(schema, strict = false) {
42
- const finalSchema = strict && schema ? toOpenAIStrictSchema(schema) : schema;
43
- const schemaStr = finalSchema ? `JSON Schema:\n${JSON.stringify(finalSchema, null, 2)}\n` : '';
44
- return `\n\n<crewx.workflow>
45
- ## ⚠️ Output Format (read first — drives branch evaluation)
1
+ "use strict";var y=exports&&exports.__createBinding||(Object.create?(function(n,e,r,t){if(t===void 0)t=r;var o=Object.getOwnPropertyDescriptor(e,r);if(!o||("get"in o?!e.__esModule:o.writable||o.configurable)){o={enumerable:true,get:function(){return e[r]}}}Object.defineProperty(n,t,o)}):(function(n,e,r,t){if(t===void 0)t=r;n[t]=e[r]}));var _=exports&&exports.__setModuleDefault||(Object.create?(function(n,e){Object.defineProperty(n,"default",{enumerable:true,value:e})}):function(n,e){n["default"]=e});var v=exports&&exports.__importStar||(function(){var n=function(e){n=Object.getOwnPropertyNames||function(r){var t=[];for(var o in r)if(Object.prototype.hasOwnProperty.call(r,o))t[t.length]=o;return t};return n(e)};return function(e){if(e&&e.__esModule)return e;var r={};if(e!=null){for(var t=n(e),o=0;o<t.length;o++)if(t[o]!=="default")y(r,e,t[o])}_(r,e);return r}})();Object.defineProperty(exports,"__esModule",{value:true});exports.WORKFLOW_SIGNAL_KEY=void 0;exports.buildWorkflowPreamble=j;exports.detectWorkflowSignal=E;exports.buildJsonOutputSuffix=P;exports.extractJson=O;exports.validateJsonSchema=W;exports.toOpenAIStrictSchema=S;const N=require("node:crypto");exports.WORKFLOW_SIGNAL_KEY="workflow_signal";function j(n){return`<crewx.workflow>
2
+ ## Execution Context
3
+ You are running headless as node "${n.nodeId}" of workflow "${n.workflowId}" (run ${n.execId}).
4
+ - No human is watching this session. NEVER ask clarifying questions or wait for
5
+ confirmation \u2014 state your assumptions and proceed, or report failure.
6
+ - Your output is consumed verbatim by downstream nodes. Output the result only;
7
+ skip greetings, progress notes, and meta commentary.
8
+ - If the task cannot be completed (broken precondition, missing data, failed
9
+ verification), do NOT pretend success or return a partial answer as if it
10
+ succeeded. Instead output exactly one JSON object and nothing else:
11
+ {"${exports.WORKFLOW_SIGNAL_KEY}": "fail", "reason": "<one-line cause>"}
12
+ The engine will halt the workflow at this node and record the reason.
13
+ </crewx.workflow>
14
+
15
+ `}function E(n){if(!n.includes(`"${exports.WORKFLOW_SIGNAL_KEY}"`))return null;const e=t=>{if(typeof t==="object"&&t!==null&&!Array.isArray(t)&&t[exports.WORKFLOW_SIGNAL_KEY]==="fail"){const o=t["reason"];return{signal:"fail",...typeof o==="string"&&o?{reason:o}:{}}}return null};try{const t=e(JSON.parse(O(n)));if(t)return t}catch{}const r=n.match(new RegExp(`\\{[^{}]*"${exports.WORKFLOW_SIGNAL_KEY}"\\s*:\\s*"fail"[^{}]*\\}`));if(r){try{return e(JSON.parse(r[0]))}catch{return{signal:"fail"}}}return null}function P(n,e=false){const r=e&&n?S(n):n;const t=r?`JSON Schema:
16
+ ${JSON.stringify(r,null,2)}
17
+ `:"";return`
18
+
19
+ <crewx.workflow>
20
+ ## \u26A0\uFE0F Output Format (read first \u2014 drives branch evaluation)
46
21
 
47
22
  **The first character of your output MUST be \`{\`. The last character MUST be \`}\`.**
48
23
  No other text is permitted (no explanations, greetings, progress notes, or analysis remarks).
49
24
  **NEVER use markdown code fences (\`\`\`json ... \`\`\`).** Do not output even a single backtick.
50
- Branch evaluation calls \`JSON.parse(state.<output>)\` directly —
25
+ Branch evaluation calls \`JSON.parse(state.<output>)\` directly \u2014
51
26
  even one non-JSON character at start or end will halt the workflow.
52
27
 
53
- ${schemaStr}Execute the task, then output **exactly one JSON object** matching the schema above. Nothing else.
54
- </crewx.workflow>`;
55
- }
56
- function extractJson(text) {
57
- let trimmed = text.trim();
58
- const fenceMatch = trimmed.match(/^```(?:json|JSON)?\s*\n?([\s\S]*?)\n?```\s*$/);
59
- if (fenceMatch && fenceMatch[1] !== undefined) {
60
- trimmed = fenceMatch[1].trim();
61
- }
62
- const objMatch = trimmed.match(/\{[\s\S]*\}/);
63
- const arrMatch = trimmed.match(/\[[\s\S]*\]/);
64
- if (objMatch && arrMatch) {
65
- return objMatch.index < arrMatch.index ? objMatch[0] : arrMatch[0];
66
- }
67
- return objMatch?.[0] ?? arrMatch?.[0] ?? trimmed;
68
- }
69
- async function validateJsonSchema(value, schema) {
70
- const { registerSchema, validate } = await Promise.resolve().then(() => __importStar(require('@hyperjump/json-schema/draft-2020-12')));
71
- const uri = `urn:crewx:schema:${(0, node_crypto_1.randomUUID)()}`;
72
- registerSchema({ $schema: 'https://json-schema.org/draft/2020-12/schema', ...schema }, uri);
73
- const result = await validate(uri, value);
74
- if (!result.valid) {
75
- const errors = (result.errors ?? [])
76
- .map((e) => e.instanceLocation)
77
- .join('; ');
78
- throw new Error(`Schema validation failed: ${errors || 'unknown error'}`);
79
- }
80
- }
81
- function toOpenAIStrictSchema(schema) {
82
- return transformStrict(schema);
83
- }
84
- const OPENAI_UNSUPPORTED_KEYWORDS = new Set([
85
- 'default', 'format', 'pattern', 'minLength', 'maxLength',
86
- 'minimum', 'maximum', 'not',
87
- ]);
88
- function transformStrict(schema) {
89
- const result = {};
90
- for (const [key, value] of Object.entries(schema)) {
91
- if (OPENAI_UNSUPPORTED_KEYWORDS.has(key))
92
- continue;
93
- if (key === 'properties' && isRecord(value)) {
94
- const transformed = {};
95
- for (const [propKey, propVal] of Object.entries(value)) {
96
- transformed[propKey] = transformStrict(propVal);
97
- }
98
- result['properties'] = transformed;
99
- result['required'] = Object.keys(transformed);
100
- result['additionalProperties'] = false;
101
- }
102
- else if (key === 'items' && isRecord(value)) {
103
- result['items'] = transformStrict(value);
104
- }
105
- else if (key === 'required') {
106
- if (!('properties' in schema)) {
107
- result['required'] = value;
108
- }
109
- }
110
- else if (key === 'additionalProperties') {
111
- result['additionalProperties'] = false;
112
- }
113
- else {
114
- result[key] = value;
115
- }
116
- }
117
- if (result['type'] === 'object' && !('additionalProperties' in result)) {
118
- result['additionalProperties'] = false;
119
- }
120
- return result;
121
- }
122
- function isRecord(val) {
123
- return typeof val === 'object' && val !== null && !Array.isArray(val);
124
- }
125
- //# sourceMappingURL=output-format.js.map
28
+ ${t}Execute the task, then output **exactly one JSON object** matching the schema above. Nothing else.
29
+ </crewx.workflow>`}function O(n){let e=n.trim();const r=e.match(/^```(?:json|JSON)?\s*\n?([\s\S]*?)\n?```\s*$/);if(r&&r[1]!==void 0){e=r[1].trim()}const t=(i,d,b)=>{const w=[];let h=false;let p=false;let l=0;let u=-1;for(let s=0;s<i.length;s++){const f=i[s];if(p){p=false;continue}if(h){if(f==="\\"){p=true;continue}if(f==='"'){h=false}continue}if(f==='"'){h=true;continue}if(f===d){if(l===0)u=s;l++}else if(f===b){if(l>0){l--;if(l===0&&u!==-1){w.push({text:i.slice(u,s+1),start:u,end:s});u=-1}}}}return w};const o=t(e,"{","}");const a=t(e,"[","]");const c=[...o,...a].sort((i,d)=>d.end-i.end);for(const i of c){try{JSON.parse(i.text);return i.text}catch{}}return e}async function W(n,e){const{registerSchema:r,validate:t}=await Promise.resolve().then(()=>v(require("@hyperjump/json-schema/draft-2020-12")));const o=`urn:crewx:schema:${(0,N.randomUUID)()}`;r({$schema:"https://json-schema.org/draft/2020-12/schema",...e},o);const a=await t(o,n);if(!a.valid){const c=(a.errors??[]).map(i=>i.instanceLocation).join("; ");throw new Error(`Schema validation failed: ${c||"unknown error"}`)}}function S(n){return m(n)}const J=new Set(["default","format","pattern","minLength","maxLength","minimum","maximum","not"]);function m(n){const e={};for(const[r,t]of Object.entries(n)){if(J.has(r))continue;if(r==="properties"&&g(t)){const o={};for(const[a,c]of Object.entries(t)){o[a]=m(c)}e["properties"]=o;e["required"]=Object.keys(o);e["additionalProperties"]=false}else if(r==="items"&&g(t)){e["items"]=m(t)}else if(r==="required"){if(!("properties"in n)){e["required"]=t}}else if(r==="additionalProperties"){e["additionalProperties"]=false}else{e[r]=t}}if(e["type"]==="object"&&!("additionalProperties"in e)){e["additionalProperties"]=false}return e}function g(n){return typeof n==="object"&&n!==null&&!Array.isArray(n)}
@@ -0,0 +1,18 @@
1
+ export declare const SKILL_NAME_RE: RegExp;
2
+ export declare const SHELL_DENYLIST: Set<string>;
3
+ export declare const INLINE_CODE_INTERPRETERS: Set<string>;
4
+ export declare const DANGEROUS_ENV_KEYS: Set<string>;
5
+ export interface CommandCheck {
6
+ ok: boolean;
7
+ error?: string;
8
+ }
9
+ export declare function validateShellCommand(command: unknown): CommandCheck;
10
+ export declare function validateEnvKeys(env: Record<string, string> | undefined): CommandCheck;
11
+ export interface CwdCheck {
12
+ ok: boolean;
13
+ resolved?: string;
14
+ error?: string;
15
+ }
16
+ export declare function normalizeCwd(cwd: string | undefined, projectRoot: string): CwdCheck;
17
+ export declare function buildShellEnv(nodeEnv: Record<string, string> | undefined, parentEnv?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
18
+
@@ -0,0 +1 @@
1
+ "use strict";var u=exports&&exports.__createBinding||(Object.create?(function(e,r,n,s){if(s===void 0)s=n;var t=Object.getOwnPropertyDescriptor(r,n);if(!t||("get"in t?!r.__esModule:t.writable||t.configurable)){t={enumerable:true,get:function(){return r[n]}}}Object.defineProperty(e,s,t)}):(function(e,r,n,s){if(s===void 0)s=n;e[s]=r[n]}));var f=exports&&exports.__setModuleDefault||(Object.create?(function(e,r){Object.defineProperty(e,"default",{enumerable:true,value:r})}):function(e,r){e["default"]=r});var l=exports&&exports.__importStar||(function(){var e=function(r){e=Object.getOwnPropertyNames||function(n){var s=[];for(var t in n)if(Object.prototype.hasOwnProperty.call(n,t))s[s.length]=t;return s};return e(r)};return function(r){if(r&&r.__esModule)return r;var n={};if(r!=null){for(var s=e(r),t=0;t<s.length;t++)if(s[t]!=="default")u(n,r,s[t])}f(n,r);return n}})();Object.defineProperty(exports,"__esModule",{value:true});exports.DANGEROUS_ENV_KEYS=exports.INLINE_CODE_INTERPRETERS=exports.SHELL_DENYLIST=exports.SKILL_NAME_RE=void 0;exports.validateShellCommand=E;exports.validateEnvKeys=h;exports.normalizeCwd=d;exports.buildShellEnv=S;const a=l(require("path"));const i=l(require("fs"));exports.SKILL_NAME_RE=/^[a-z0-9][a-z0-9-]*$/;exports.SHELL_DENYLIST=new Set(["bash","sh","zsh","dash","fish","cmd","cmd.exe","powershell","powershell.exe","pwsh","pwsh.exe"]);exports.INLINE_CODE_INTERPRETERS=new Set(["python","python3","node","nodejs","deno","perl","ruby","php"]);const c=new Set(["-c","-e","--eval","--exec"]);exports.DANGEROUS_ENV_KEYS=new Set(["LD_PRELOAD","LD_LIBRARY_PATH","LD_AUDIT","DYLD_INSERT_LIBRARIES","DYLD_LIBRARY_PATH","DYLD_FRAMEWORK_PATH","NODE_OPTIONS"]);function _(e){return a.basename(e).toLowerCase()}function E(e){if(!Array.isArray(e)){return{ok:false,error:"shell_task.command must be an array of strings (argv), not a string"}}if(e.length===0){return{ok:false,error:"shell_task.command must not be empty"}}if(!e.every(s=>typeof s==="string")){return{ok:false,error:"shell_task.command must contain only strings"}}const r=_(e[0]);const n=r.replace(/\.exe$/,"");if(exports.SHELL_DENYLIST.has(r)||exports.SHELL_DENYLIST.has(n)){return{ok:false,error:`shell_task.command[0] "${e[0]}" is a blocked shell interpreter`}}if(exports.INLINE_CODE_INTERPRETERS.has(n)){const s=e.slice(1).some(t=>c.has(String(t)));if(s){return{ok:false,error:`shell_task.command "${n} ${e[1]}" uses an inline-code flag (escape vector blocked)`}}}return{ok:true}}function h(e){if(!e)return{ok:true};for(const r of Object.keys(e)){if(exports.DANGEROUS_ENV_KEYS.has(r)){return{ok:false,error:`shell_task.env key "${r}" is not allowed`}}}return{ok:true}}function d(e,r){let n;try{n=i.realpathSync(r)}catch(o){return{ok:false,error:`project root "${r}" is not accessible: ${o.message}`}}if(e===void 0||e===""){return{ok:true,resolved:n}}const s=a.resolve(n,e);let t;try{t=i.realpathSync(s)}catch(o){return{ok:false,error:`cwd "${e}" is not accessible: ${o.message}`}}if(t!==n&&!t.startsWith(n+a.sep)){return{ok:false,error:`cwd "${e}" resolves outside the project root`}}return{ok:true,resolved:t}}function S(e,r=process.env){const n=process.platform==="win32"?["PATH","Path","SystemRoot","COMSPEC","TEMP","TMP"]:["PATH","HOME","LANG","TMPDIR"];const s={};for(const t of n){if(r[t]!==void 0)s[t]=r[t]}if(e){for(const[t,o]of Object.entries(e)){s[t]=o}}return s}
@@ -0,0 +1,48 @@
1
+ import type { JsonObject, UiAuditSnapshotInput, UiInstance } from '@crewx/sdk';
2
+ import { type WorkflowApprovalState } from './card-state.js';
3
+ import { type WorkflowParentLifecycleChange, type WorkflowParentPersistTaskRepository, type WorkflowParentTaskKind } from './parent-task-sync.js';
4
+ import type { RunExecution, WorkflowSpec } from './types.js';
5
+ export interface WorkflowAuditSnapshotStore {
6
+ get(instanceId: string): UiInstance | null;
7
+ updateAuditSnapshot(input: UiAuditSnapshotInput): UiInstance | null;
8
+ }
9
+ export interface WorkflowPersistenceProjectionResult {
10
+ runId: string;
11
+ changedParentTaskIds: string[];
12
+ projectedAuditInstanceIds: string[];
13
+ errors: string[];
14
+ }
15
+ export interface WorkflowAuditSnapshotChange {
16
+ instanceId: string;
17
+ before: JsonObject;
18
+ after: WorkflowApprovalState;
19
+ }
20
+ export interface WorkflowPersistenceProjectionPreview {
21
+ runId: string;
22
+ runStatus: NonNullable<RunExecution['status']>;
23
+ parentChanges: WorkflowParentLifecycleChange[];
24
+ auditSnapshotChanges: WorkflowAuditSnapshotChange[];
25
+ errors: string[];
26
+ }
27
+ export interface WorkflowPersistenceProjectionPreviewOptions {
28
+ taskRepo: Pick<WorkflowParentPersistTaskRepository, 'findWorkflowParentTask'>;
29
+ workspaceId: string;
30
+ resolveWorkflowSpec: (run: RunExecution) => WorkflowSpec | null | undefined;
31
+ uiInstanceStore?: Pick<WorkflowAuditSnapshotStore, 'get'>;
32
+ getUiInstanceStore?: () => Pick<WorkflowAuditSnapshotStore, 'get'>;
33
+ parentKinds?: readonly WorkflowParentTaskKind[];
34
+ }
35
+ export interface WorkflowPersistenceProjectionOptions {
36
+ taskRepo: WorkflowParentPersistTaskRepository;
37
+ workspaceId: string;
38
+ resolveWorkflowSpec: (run: RunExecution) => WorkflowSpec | null | undefined;
39
+ uiInstanceStore?: WorkflowAuditSnapshotStore;
40
+ getUiInstanceStore?: () => WorkflowAuditSnapshotStore;
41
+ parentKinds?: readonly WorkflowParentTaskKind[];
42
+ onChanged?: (result: WorkflowPersistenceProjectionResult) => void;
43
+ warn?: (message: string) => void;
44
+ }
45
+ export type WorkflowPersistenceProjection = (run: RunExecution) => WorkflowPersistenceProjectionResult;
46
+ export declare function createWorkflowPersistenceProjection(options: WorkflowPersistenceProjectionOptions): WorkflowPersistenceProjection;
47
+ export declare function previewWorkflowPersistenceProjection(options: WorkflowPersistenceProjectionPreviewOptions, run: RunExecution): WorkflowPersistenceProjectionPreview;
48
+
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.createWorkflowPersistenceProjection=b;exports.previewWorkflowPersistenceProjection=m;const g=require("./card-state.js");const S=require("./parent-task-sync.js");function h(e){return e instanceof Error?e.message:String(e)}function _(e){return e}function w(e){if(Array.isArray(e))return`[${e.map(r=>w(r)).join(",")}]`;if(e!==null&&typeof e==="object"){const r=e;return`{${Object.keys(r).sort().map(i=>`${JSON.stringify(i)}:${w(r[i])}`).join(",")}}`}return JSON.stringify(e)??"null"}function A(e,r){return w(e)===w(r)}function P(e,r){return{runId:e,changedParentTaskIds:[...r.changedParentTaskIds],projectedAuditInstanceIds:[],errors:[]}}function b(e){const r={taskRepo:e.taskRepo,workspaceId:e.workspaceId,...e.parentKinds?{parentKinds:e.parentKinds}:{}};const i=(0,S.createWorkflowParentPersistSink)(r);const o=e.warn??(n=>console.warn(n));return n=>{let u;try{u=i(n)}catch(s){const t=`workflow persistence parent projection failed for ${n.id}: ${h(s)}`;o(t);return{runId:n.id,changedParentTaskIds:[],projectedAuditInstanceIds:[],errors:[t]}}const c=P(n.id,u);const d=(s,t)=>{const a=`workflow persistence ${s} failed for ${n.id}: ${h(t)}`;c.errors.push(a);o(a)};const p=[...new Set(u.linkedAuditInstanceIds)];if(p.length>0){let s;try{s=e.resolveWorkflowSpec(n);if(!s)throw new Error(`workflow spec not found for ${n.workflow_id}`)}catch(t){d("spec resolution",t)}if(s){let t=null;try{t=(0,g.buildWorkflowApprovalState)(n,s)}catch(a){d("audit state build",a)}if(t){const a=Date.parse(n.updated_at);if(!Number.isSafeInteger(a)||a<0){d("audit snapshot",new Error(`invalid run updated_at: ${n.updated_at}`))}else{let I=e.uiInstanceStore;if(!I&&e.getUiInstanceStore){try{I=e.getUiInstanceStore()}catch(l){d("audit store creation",l)}}if(!I){d("audit snapshot",new Error("no UiInstanceStore was provided"))}else{for(const l of p){try{const f=I.get(l);const $=f?.domainRef?.workflowRunId;if(!f||f.workspaceId!==e.workspaceId||$!==n.id){continue}const j=w(f.state);const y=f.updatedAt;const k=I.updateAuditSnapshot({instanceId:l,workspaceId:e.workspaceId,updatedAt:a,state:_(t)});if(k&&(w(k.state)!==j||k.updatedAt!==y)){c.projectedAuditInstanceIds.push(l)}}catch(f){d(`audit snapshot ${l}`,f)}}}}}}}if((c.changedParentTaskIds.length>0||c.projectedAuditInstanceIds.length>0)&&e.onChanged){try{e.onChanged(c)}catch(s){d("change callback",s)}}return c}}function m(e,r){const i=(0,S.previewWorkflowParentRepair)({taskRepo:e.taskRepo,run:r,workspaceId:e.workspaceId,...e.parentKinds?{parentKinds:e.parentKinds}:{}});const o={runId:r.id,runStatus:i.runStatus,parentChanges:[...i.changes],auditSnapshotChanges:[],errors:[]};const n=(t,a)=>{o.errors.push(`workflow persistence preview ${t} failed for ${r.id}: ${h(a)}`)};const u=[...new Set(i.linkedAuditInstanceIds)];if(u.length===0)return o;let c;try{c=e.resolveWorkflowSpec(r);if(!c)throw new Error(`workflow spec not found for ${r.workflow_id}`)}catch(t){n("spec resolution",t)}if(!c)return o;let d;try{d=(0,g.buildWorkflowApprovalState)(r,c)}catch(t){n("audit state build",t);return o}const p=Date.parse(r.updated_at);if(!Number.isSafeInteger(p)||p<0){n("audit snapshot",new Error(`invalid run updated_at: ${r.updated_at}`));return o}let s=e.uiInstanceStore;if(!s&&e.getUiInstanceStore){try{s=e.getUiInstanceStore()}catch(t){n("audit store creation",t)}}if(!s){n("audit snapshot",new Error("no UiInstanceStore was provided"));return o}for(const t of u){try{const a=s.get(t);if(!a||a.workspaceId!==e.workspaceId||a.domainRef.workflowRunId!==r.id){continue}if(!A(a.state,d)){o.auditSnapshotChanges.push({instanceId:t,before:a.state,after:d})}}catch(a){n(`audit snapshot ${t}`,a)}}void p;return o}
@@ -43,6 +43,10 @@
43
43
  },
44
44
  "version": {
45
45
  "type": "string"
46
+ },
47
+ "shell_task_allowed": {
48
+ "type": "boolean",
49
+ "description": "Opt-in gate (2nd factor) required to execute shell_task nodes. Combined with env CREWX_WORKFLOW_SHELL=1."
46
50
  }
47
51
  }
48
52
  },
@@ -52,11 +56,44 @@
52
56
  "properties": {
53
57
  "type": {
54
58
  "type": "string",
55
- "enum": ["agent_task", "parallel", "join", "branch", "approval", "expression", "end"]
59
+ "enum": ["agent_task", "skill_task", "shell_task", "parallel", "join", "branch", "approval", "expression", "end", "error"]
56
60
  },
57
61
  "agent": {
58
62
  "type": "string"
59
63
  },
64
+ "skill": {
65
+ "type": "string",
66
+ "description": "skill_task: built-in/registered skill name. Must match ^[a-z0-9][a-z0-9-]*$ (no path traversal)."
67
+ },
68
+ "args": {
69
+ "type": "array",
70
+ "items": {
71
+ "type": "string"
72
+ },
73
+ "description": "skill_task: extra CLI args appended after the skill name. Supports {{template}} interpolation."
74
+ },
75
+ "command": {
76
+ "type": "array",
77
+ "items": {
78
+ "type": "string"
79
+ },
80
+ "description": "shell_task: argv array (never a plain string). command[0] is the program. Interpolated."
81
+ },
82
+ "cwd": {
83
+ "type": "string",
84
+ "description": "agent_task/skill_task/shell_task: working directory, normalized and constrained under the project root."
85
+ },
86
+ "env": {
87
+ "type": "object",
88
+ "additionalProperties": {
89
+ "type": "string"
90
+ },
91
+ "description": "shell_task: explicit env pass-through. Dangerous keys (LD_PRELOAD, etc.) are rejected."
92
+ },
93
+ "stdin": {
94
+ "type": "string",
95
+ "description": "skill_task/shell_task: optional stdin payload. Supports {{template}} interpolation."
96
+ },
60
97
  "mode": {
61
98
  "type": "string",
62
99
  "enum": ["query", "execute"]
@@ -76,6 +113,10 @@
76
113
  "retry": {
77
114
  "$ref": "#/definitions/RetryConfig"
78
115
  },
116
+ "fail_when": {
117
+ "type": "string",
118
+ "description": "Task nodes: semantic failure judgment. Safe expression evaluated against state after the node's output is stored (e.g. 'state.verify_result.status !== \"pass\"'). Truthy → node fails and the run halts."
119
+ },
79
120
  "condition": {
80
121
  "type": "string"
81
122
  },
@@ -125,6 +166,10 @@
125
166
  "comment": {
126
167
  "type": "string"
127
168
  },
169
+ "message": {
170
+ "type": "string",
171
+ "description": "error: failure message recorded in run.error. Supports {{template}} interpolation."
172
+ },
128
173
  "on_approve": {
129
174
  "type": "string"
130
175
  },
@@ -177,19 +222,36 @@
177
222
  "required": ["max", "delay"],
178
223
  "properties": {
179
224
  "max": {
180
- "type": "number"
225
+ "type": "integer",
226
+ "minimum": 0,
227
+ "maximum": 10,
228
+ "description": "Number of additional retries after the first attempt (0 disables retry)."
181
229
  },
182
230
  "delay": {
183
231
  "type": "string",
184
232
  "enum": ["exponential", "fixed"]
185
233
  },
186
234
  "max_delay": {
187
- "type": "number"
235
+ "type": "integer",
236
+ "minimum": 0,
237
+ "description": "Maximum retry delay in milliseconds; runtime hard-caps this at 60000."
188
238
  },
189
239
  "on": {
190
240
  "type": "array",
191
241
  "items": {
192
- "type": "string"
242
+ "type": "string",
243
+ "enum": [
244
+ "CLI_UNRESOLVABLE",
245
+ "SPAWN_FAILED",
246
+ "NODE_TIMEOUT",
247
+ "NODE_EXIT_NONZERO",
248
+ "OUTPUT_INVALID",
249
+ "RATE_LIMITED",
250
+ "CONFIG_INVALID",
251
+ "INTERRUPTED",
252
+ "CANCELLED",
253
+ "UNKNOWN"
254
+ ]
193
255
  }
194
256
  }
195
257
  }
package/package.json CHANGED
@@ -1,10 +1,23 @@
1
1
  {
2
2
  "name": "@crewx/workflow",
3
- "version": "0.3.22-rc.2",
3
+ "version": "0.3.22-rc.200",
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",
7
7
  "types": "dist/src/engine.d.ts",
8
+ "typesVersions": {
9
+ "*": {
10
+ "run-manager": [
11
+ "dist/src/run-manager.d.ts"
12
+ ],
13
+ "types": [
14
+ "dist/src/types.d.ts"
15
+ ],
16
+ "list": [
17
+ "dist/src/list-view.d.ts"
18
+ ]
19
+ }
20
+ },
8
21
  "exports": {
9
22
  ".": {
10
23
  "types": "./dist/src/engine.d.ts",
@@ -12,10 +25,28 @@
12
25
  "require": "./dist/src/engine.js",
13
26
  "default": "./dist/src/engine.js"
14
27
  },
28
+ "./run-manager": {
29
+ "types": "./dist/src/run-manager.d.ts",
30
+ "import": "./dist/src/run-manager.js",
31
+ "require": "./dist/src/run-manager.js",
32
+ "default": "./dist/src/run-manager.js"
33
+ },
34
+ "./types": {
35
+ "types": "./dist/src/types.d.ts",
36
+ "import": "./dist/src/types.js",
37
+ "require": "./dist/src/types.js",
38
+ "default": "./dist/src/types.js"
39
+ },
15
40
  "./cli": {
16
41
  "import": "./dist/cli.js",
17
42
  "require": "./dist/cli.js",
18
43
  "default": "./dist/cli.js"
44
+ },
45
+ "./list": {
46
+ "types": "./dist/src/list-view.d.ts",
47
+ "import": "./dist/src/list-view.js",
48
+ "require": "./dist/src/list-view.js",
49
+ "default": "./dist/src/list-view.js"
19
50
  }
20
51
  },
21
52
  "bin": {
@@ -36,9 +67,9 @@
36
67
  "dependencies": {
37
68
  "@hyperjump/json-schema": "^1.9.4",
38
69
  "js-yaml": "^4.1.0",
39
- "@crewx/knowledge-core": "0.1.17",
40
- "@crewx/shared": "0.0.5",
41
- "@crewx/sdk": "0.8.8-rc.22"
70
+ "@crewx/knowledge-core": "0.1.17-rc.164",
71
+ "@crewx/shared": "0.0.6-rc.69",
72
+ "@crewx/sdk": "0.9.0-rc.154"
42
73
  },
43
74
  "devDependencies": {
44
75
  "@types/js-yaml": "^4.0.9",