@hexabot-ai/agentic 3.0.1-alpha.0

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 (176) hide show
  1. package/LICENSE.md +120 -0
  2. package/README.md +231 -0
  3. package/dist/cjs/action/abstract-action.js +141 -0
  4. package/dist/cjs/action/action.js +47 -0
  5. package/dist/cjs/action/action.types.js +7 -0
  6. package/dist/cjs/bindings/base-binding.js +278 -0
  7. package/dist/cjs/context.js +79 -0
  8. package/dist/cjs/dsl.types.js +318 -0
  9. package/dist/cjs/index.js +52 -0
  10. package/dist/cjs/runner-runtime-control.js +343 -0
  11. package/dist/cjs/step-executors/conditional-executor.js +63 -0
  12. package/dist/cjs/step-executors/loop-executor.js +187 -0
  13. package/dist/cjs/step-executors/parallel-executor.js +51 -0
  14. package/dist/cjs/step-executors/skip-helpers.js +29 -0
  15. package/dist/cjs/step-executors/task-executor.js +155 -0
  16. package/dist/cjs/step-executors/types.js +7 -0
  17. package/dist/cjs/suspension-rebuilder.js +301 -0
  18. package/dist/cjs/utils/deferred.js +17 -0
  19. package/dist/cjs/utils/naming.js +48 -0
  20. package/dist/cjs/utils/timeout.js +42 -0
  21. package/dist/cjs/utils/workflow-definition.js +102 -0
  22. package/dist/cjs/workflow-compiler.js +212 -0
  23. package/dist/cjs/workflow-event-emitter.js +40 -0
  24. package/dist/cjs/workflow-runner.js +448 -0
  25. package/dist/cjs/workflow-types.js +7 -0
  26. package/dist/cjs/workflow-values.js +99 -0
  27. package/dist/cjs/workflow.js +243 -0
  28. package/dist/esm/action/abstract-action.js +137 -0
  29. package/dist/esm/action/action.js +44 -0
  30. package/dist/esm/action/action.types.js +6 -0
  31. package/dist/esm/bindings/base-binding.js +273 -0
  32. package/dist/esm/context.js +75 -0
  33. package/dist/esm/dsl.types.js +309 -0
  34. package/dist/esm/index.js +16 -0
  35. package/dist/esm/runner-runtime-control.js +338 -0
  36. package/dist/esm/step-executors/conditional-executor.js +60 -0
  37. package/dist/esm/step-executors/loop-executor.js +182 -0
  38. package/dist/esm/step-executors/parallel-executor.js +48 -0
  39. package/dist/esm/step-executors/skip-helpers.js +25 -0
  40. package/dist/esm/step-executors/task-executor.js +152 -0
  41. package/dist/esm/step-executors/types.js +6 -0
  42. package/dist/esm/suspension-rebuilder.js +296 -0
  43. package/dist/esm/utils/deferred.js +14 -0
  44. package/dist/esm/utils/naming.js +42 -0
  45. package/dist/esm/utils/timeout.js +37 -0
  46. package/dist/esm/utils/workflow-definition.js +98 -0
  47. package/dist/esm/workflow-compiler.js +208 -0
  48. package/dist/esm/workflow-event-emitter.js +36 -0
  49. package/dist/esm/workflow-runner.js +444 -0
  50. package/dist/esm/workflow-types.js +6 -0
  51. package/dist/esm/workflow-values.js +89 -0
  52. package/dist/esm/workflow.js +236 -0
  53. package/dist/types/action/abstract-action.d.ts +54 -0
  54. package/dist/types/action/abstract-action.d.ts.map +1 -0
  55. package/dist/types/action/action.d.ts +23 -0
  56. package/dist/types/action/action.d.ts.map +1 -0
  57. package/dist/types/action/action.types.d.ts +55 -0
  58. package/dist/types/action/action.types.d.ts.map +1 -0
  59. package/dist/types/bindings/base-binding.d.ts +52 -0
  60. package/dist/types/bindings/base-binding.d.ts.map +1 -0
  61. package/dist/types/context.d.ts +105 -0
  62. package/dist/types/context.d.ts.map +1 -0
  63. package/dist/types/dsl.types.d.ts +220 -0
  64. package/dist/types/dsl.types.d.ts.map +1 -0
  65. package/dist/types/index.d.ts +18 -0
  66. package/dist/types/index.d.ts.map +1 -0
  67. package/dist/types/runner-runtime-control.d.ts +64 -0
  68. package/dist/types/runner-runtime-control.d.ts.map +1 -0
  69. package/dist/types/step-executors/conditional-executor.d.ts +13 -0
  70. package/dist/types/step-executors/conditional-executor.d.ts.map +1 -0
  71. package/dist/types/step-executors/loop-executor.d.ts +33 -0
  72. package/dist/types/step-executors/loop-executor.d.ts.map +1 -0
  73. package/dist/types/step-executors/parallel-executor.d.ts +14 -0
  74. package/dist/types/step-executors/parallel-executor.d.ts.map +1 -0
  75. package/dist/types/step-executors/skip-helpers.d.ts +12 -0
  76. package/dist/types/step-executors/skip-helpers.d.ts.map +1 -0
  77. package/dist/types/step-executors/task-executor.d.ts +13 -0
  78. package/dist/types/step-executors/task-executor.d.ts.map +1 -0
  79. package/dist/types/step-executors/types.d.ts +24 -0
  80. package/dist/types/step-executors/types.d.ts.map +1 -0
  81. package/dist/types/suspension-rebuilder.d.ts +67 -0
  82. package/dist/types/suspension-rebuilder.d.ts.map +1 -0
  83. package/dist/types/utils/deferred.d.ts +7 -0
  84. package/dist/types/utils/deferred.d.ts.map +1 -0
  85. package/dist/types/utils/naming.d.ts +24 -0
  86. package/dist/types/utils/naming.d.ts.map +1 -0
  87. package/dist/types/utils/timeout.d.ts +17 -0
  88. package/dist/types/utils/timeout.d.ts.map +1 -0
  89. package/dist/types/utils/workflow-definition.d.ts +3 -0
  90. package/dist/types/utils/workflow-definition.d.ts.map +1 -0
  91. package/dist/types/workflow-compiler.d.ts +12 -0
  92. package/dist/types/workflow-compiler.d.ts.map +1 -0
  93. package/dist/types/workflow-event-emitter.d.ts +70 -0
  94. package/dist/types/workflow-event-emitter.d.ts.map +1 -0
  95. package/dist/types/workflow-runner.d.ts +179 -0
  96. package/dist/types/workflow-runner.d.ts.map +1 -0
  97. package/dist/types/workflow-types.d.ts +195 -0
  98. package/dist/types/workflow-types.d.ts.map +1 -0
  99. package/dist/types/workflow-values.d.ts +34 -0
  100. package/dist/types/workflow-values.d.ts.map +1 -0
  101. package/dist/types/workflow.d.ts +75 -0
  102. package/dist/types/workflow.d.ts.map +1 -0
  103. package/examples/defs-bindings-agent/actions/ai-agent.ts +110 -0
  104. package/examples/defs-bindings-agent/actions/caculate-score.ts +49 -0
  105. package/examples/defs-bindings-agent/actions/index.ts +15 -0
  106. package/examples/defs-bindings-agent/bindings.ts +20 -0
  107. package/examples/defs-bindings-agent/context.ts +32 -0
  108. package/examples/defs-bindings-agent/workflow.ts +67 -0
  109. package/examples/defs-bindings-agent/workflow.yml +28 -0
  110. package/examples/full/actions/await-user-input.ts +46 -0
  111. package/examples/full/actions/call-llm.ts +133 -0
  112. package/examples/full/actions/create-ticket.ts +46 -0
  113. package/examples/full/actions/decision-router.ts +83 -0
  114. package/examples/full/actions/get-calendar-events.ts +55 -0
  115. package/examples/full/actions/get-user-profile.ts +72 -0
  116. package/examples/full/actions/index.ts +33 -0
  117. package/examples/full/actions/query-memory.ts +45 -0
  118. package/examples/full/actions/search-web.ts +53 -0
  119. package/examples/full/actions/send-email.ts +50 -0
  120. package/examples/full/context.ts +41 -0
  121. package/examples/full/workflow.ts +75 -0
  122. package/examples/full/workflow.yml +359 -0
  123. package/examples/loop/actions/await-reply.ts +47 -0
  124. package/examples/loop/actions/index.ts +19 -0
  125. package/examples/loop/actions/send-text-message.ts +40 -0
  126. package/examples/loop/context.ts +32 -0
  127. package/examples/loop/workflow.ts +66 -0
  128. package/examples/loop/workflow.yml +66 -0
  129. package/examples/suspend-resume/actions/format-reply.ts +43 -0
  130. package/examples/suspend-resume/actions/index.ts +13 -0
  131. package/examples/suspend-resume/actions/wait-for-user.ts +51 -0
  132. package/examples/suspend-resume/context.ts +32 -0
  133. package/examples/suspend-resume/workflow.ts +79 -0
  134. package/examples/suspend-resume/workflow.yml +29 -0
  135. package/package.json +60 -0
  136. package/src/__tests__/parser.test.ts +95 -0
  137. package/src/__tests__/suspension-rebuilder.test.ts +361 -0
  138. package/src/__tests__/test-helpers.ts +36 -0
  139. package/src/__tests__/validation.test.ts +526 -0
  140. package/src/__tests__/workflow-compiler.test.ts +715 -0
  141. package/src/__tests__/workflow-definition-path.test.ts +381 -0
  142. package/src/__tests__/workflow-event-emitter.test.ts +50 -0
  143. package/src/__tests__/workflow-runner.test.ts +1397 -0
  144. package/src/__tests__/workflow-values.test.ts +132 -0
  145. package/src/__tests__/workflow.test.ts +320 -0
  146. package/src/action/__tests__/abstract-action-timing.test.ts +183 -0
  147. package/src/action/__tests__/action.test.ts +473 -0
  148. package/src/action/abstract-action.ts +215 -0
  149. package/src/action/action.ts +83 -0
  150. package/src/action/action.types.ts +93 -0
  151. package/src/bindings/base-binding.ts +508 -0
  152. package/src/context.ts +179 -0
  153. package/src/dsl.types.ts +473 -0
  154. package/src/index.ts +114 -0
  155. package/src/runner-runtime-control.ts +544 -0
  156. package/src/step-executors/conditional-executor.test.ts +192 -0
  157. package/src/step-executors/conditional-executor.ts +83 -0
  158. package/src/step-executors/loop-executor.test.ts +303 -0
  159. package/src/step-executors/loop-executor.ts +299 -0
  160. package/src/step-executors/parallel-executor.test.ts +182 -0
  161. package/src/step-executors/parallel-executor.ts +77 -0
  162. package/src/step-executors/skip-helpers.ts +68 -0
  163. package/src/step-executors/task-executor.test.ts +257 -0
  164. package/src/step-executors/task-executor.ts +248 -0
  165. package/src/step-executors/types.ts +70 -0
  166. package/src/suspension-rebuilder.ts +504 -0
  167. package/src/utils/deferred.ts +23 -0
  168. package/src/utils/naming.ts +55 -0
  169. package/src/utils/timeout.ts +48 -0
  170. package/src/utils/workflow-definition.ts +173 -0
  171. package/src/workflow-compiler.ts +317 -0
  172. package/src/workflow-event-emitter.ts +86 -0
  173. package/src/workflow-runner.ts +603 -0
  174. package/src/workflow-types.ts +207 -0
  175. package/src/workflow-values.ts +159 -0
  176. package/src/workflow.ts +387 -0
@@ -0,0 +1,173 @@
1
+ /*
2
+ * Hexabot — Fair Core License (FCL-1.0-ALv2)
3
+ * Copyright (c) 2025 Hexastack.
4
+ * Full terms: see LICENSE.md.
5
+ */
6
+
7
+ import {
8
+ TASK_KIND,
9
+ type FlowStep,
10
+ type WorkflowDefinition,
11
+ } from '../dsl.types';
12
+
13
+ const escapeRegExp = (value: string): string =>
14
+ value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
15
+ const replaceOutputTaskReferences = (
16
+ source: string,
17
+ currentTaskName: string,
18
+ nextTaskName: string,
19
+ ): string => {
20
+ if (!source.includes('$output')) {
21
+ return source;
22
+ }
23
+
24
+ const escapedTaskName = escapeRegExp(currentTaskName);
25
+ const dotNotationPattern = new RegExp(
26
+ `(\\$output\\.)${escapedTaskName}(?=[^a-zA-Z0-9_]|$)`,
27
+ 'g',
28
+ );
29
+ const singleQuotePattern = new RegExp(
30
+ `(\\$output\\s*\\[\\s*')${escapedTaskName}('\\s*\\])`,
31
+ 'g',
32
+ );
33
+ const doubleQuotePattern = new RegExp(
34
+ `(\\$output\\s*\\[\\s*")${escapedTaskName}("\\s*\\])`,
35
+ 'g',
36
+ );
37
+
38
+ return source
39
+ .replace(dotNotationPattern, `$1${nextTaskName}`)
40
+ .replace(singleQuotePattern, `$1${nextTaskName}$2`)
41
+ .replace(doubleQuotePattern, `$1${nextTaskName}$2`);
42
+ };
43
+ const renameTaskInFlow = (
44
+ steps: WorkflowDefinition['flow'],
45
+ currentTaskName: string,
46
+ nextTaskName: string,
47
+ ): WorkflowDefinition['flow'] => {
48
+ return steps.map((step): FlowStep => {
49
+ if ('do' in step) {
50
+ return {
51
+ ...step,
52
+ do: step.do === currentTaskName ? nextTaskName : step.do,
53
+ };
54
+ }
55
+
56
+ if ('parallel' in step) {
57
+ return {
58
+ ...step,
59
+ parallel: {
60
+ ...step.parallel,
61
+ steps: renameTaskInFlow(
62
+ step.parallel.steps,
63
+ currentTaskName,
64
+ nextTaskName,
65
+ ),
66
+ },
67
+ };
68
+ }
69
+
70
+ if ('conditional' in step) {
71
+ return {
72
+ ...step,
73
+ conditional: {
74
+ ...step.conditional,
75
+ when: step.conditional.when.map((branch) => ({
76
+ ...branch,
77
+ steps: renameTaskInFlow(
78
+ branch.steps,
79
+ currentTaskName,
80
+ nextTaskName,
81
+ ),
82
+ })),
83
+ },
84
+ };
85
+ }
86
+
87
+ if ('loop' in step) {
88
+ return {
89
+ ...step,
90
+ loop: {
91
+ ...step.loop,
92
+ steps: renameTaskInFlow(
93
+ step.loop.steps,
94
+ currentTaskName,
95
+ nextTaskName,
96
+ ),
97
+ },
98
+ };
99
+ }
100
+
101
+ return step;
102
+ });
103
+ };
104
+ const renameOutputTaskReferencesInValue = (
105
+ value: unknown,
106
+ currentTaskName: string,
107
+ nextTaskName: string,
108
+ ): unknown => {
109
+ if (typeof value === 'string') {
110
+ return replaceOutputTaskReferences(value, currentTaskName, nextTaskName);
111
+ }
112
+
113
+ if (Array.isArray(value)) {
114
+ return value.map((item) =>
115
+ renameOutputTaskReferencesInValue(item, currentTaskName, nextTaskName),
116
+ );
117
+ }
118
+
119
+ if (!value || typeof value !== 'object') {
120
+ return value;
121
+ }
122
+
123
+ return Object.entries(value).reduce<Record<string, unknown>>(
124
+ (acc, [key, nestedValue]) => {
125
+ acc[key] = renameOutputTaskReferencesInValue(
126
+ nestedValue,
127
+ currentTaskName,
128
+ nextTaskName,
129
+ );
130
+
131
+ return acc;
132
+ },
133
+ {},
134
+ );
135
+ };
136
+
137
+ export const safeRenameTaskInDefinition = (
138
+ definition: WorkflowDefinition,
139
+ currentTaskName: string,
140
+ nextTaskName: string,
141
+ ): WorkflowDefinition => {
142
+ if (currentTaskName === nextTaskName) {
143
+ return definition;
144
+ }
145
+
146
+ if (!Object.prototype.hasOwnProperty.call(definition.defs, currentTaskName)) {
147
+ return definition;
148
+ }
149
+
150
+ const currentDefinition = definition.defs[currentTaskName];
151
+ if (!currentDefinition || currentDefinition.kind !== TASK_KIND) {
152
+ return definition;
153
+ }
154
+
155
+ const renamedDefs = Object.entries(definition.defs).reduce<
156
+ WorkflowDefinition['defs']
157
+ >((acc, [defName, def]) => {
158
+ acc[defName === currentTaskName ? nextTaskName : defName] = def;
159
+
160
+ return acc;
161
+ }, {});
162
+ const definitionWithRenamedFlow: WorkflowDefinition = {
163
+ ...definition,
164
+ defs: renamedDefs,
165
+ flow: renameTaskInFlow(definition.flow, currentTaskName, nextTaskName),
166
+ };
167
+
168
+ return renameOutputTaskReferencesInValue(
169
+ definitionWithRenamedFlow,
170
+ currentTaskName,
171
+ nextTaskName,
172
+ ) as WorkflowDefinition;
173
+ };
@@ -0,0 +1,317 @@
1
+ /*
2
+ * Hexabot — Fair Core License (FCL-1.0-ALv2)
3
+ * Copyright (c) 2025 Hexastack.
4
+ * Full terms: see LICENSE.md.
5
+ */
6
+
7
+ import { z, ZodType } from 'zod';
8
+
9
+ import type { Action } from './action/action.types';
10
+ import type { BindingKindSchemas } from './bindings/base-binding';
11
+ import {
12
+ mountTaskBindings,
13
+ validateAndResolveBindings,
14
+ } from './bindings/base-binding';
15
+ import type { BaseWorkflowContext } from './context';
16
+ import type {
17
+ FlowStep,
18
+ InputField,
19
+ TaskDefinition,
20
+ WorkflowDefinition,
21
+ } from './dsl.types';
22
+ import { extractTaskDefinitions as extractTaskDefinitionsFromDefs } from './dsl.types';
23
+ import { assertSnakeCaseName } from './utils/naming';
24
+ import { StepType } from './workflow-event-emitter';
25
+ import type {
26
+ CompiledMapping,
27
+ CompiledStep,
28
+ CompiledTask,
29
+ CompiledWorkflow,
30
+ ConditionalBranch,
31
+ LoopStep,
32
+ ParallelStep,
33
+ } from './workflow-types';
34
+ import {
35
+ compileValue,
36
+ mergeSettings,
37
+ type CompileValueOptions,
38
+ } from './workflow-values';
39
+
40
+ export type WorkflowCompileOptions = CompileValueOptions & {
41
+ actions: Record<string, Action>;
42
+ bindingKinds?: BindingKindSchemas;
43
+ };
44
+
45
+ /** Build a stable identifier for a step using its path and label. */
46
+ const buildStepId = (path: Array<number | string>, label: string): string => {
47
+ const pathPart = path.length > 0 ? path.join('.') : 'root';
48
+
49
+ return `${pathPart}:${label}`;
50
+ };
51
+ /** Convert workflow input field metadata to a zod schema. */
52
+ const inputFieldToZod = (field: InputField): ZodType => {
53
+ let schema: ZodType;
54
+
55
+ switch (field.type) {
56
+ case 'string':
57
+ schema = z.string();
58
+ break;
59
+ case 'number':
60
+ schema = z.number();
61
+ break;
62
+ case 'integer':
63
+ schema = z.int();
64
+ break;
65
+ case 'boolean':
66
+ schema = z.boolean();
67
+ break;
68
+ case 'array':
69
+ schema = z.array(field.items ? inputFieldToZod(field.items) : z.any());
70
+ break;
71
+ case 'object': {
72
+ const properties: Record<string, ZodType> = {};
73
+ if (field.properties) {
74
+ for (const [name, child] of Object.entries(field.properties)) {
75
+ properties[name] = inputFieldToZod(child).optional();
76
+ }
77
+ }
78
+ schema =
79
+ Object.keys(properties).length > 0
80
+ ? z.strictObject(properties).partial()
81
+ : z.record(z.string(), z.any());
82
+ break;
83
+ }
84
+ default:
85
+ schema = z.any();
86
+ }
87
+
88
+ if (field.enum) {
89
+ schema = schema.refine(
90
+ (value) => field.enum?.some((allowed) => allowed === value),
91
+ {
92
+ message: `Value must be one of: ${field.enum.join(', ')}`,
93
+ },
94
+ );
95
+ }
96
+
97
+ return schema;
98
+ };
99
+ /** Assemble a parser for the workflow top-level input payload. */
100
+ const buildInputParser = (
101
+ schema?: Record<string, InputField>,
102
+ ): ZodType<Record<string, unknown>> => {
103
+ if (!schema || Object.keys(schema).length === 0) {
104
+ return z.looseObject({});
105
+ }
106
+
107
+ const shape: Record<string, ZodType> = {};
108
+
109
+ for (const [name, field] of Object.entries(schema)) {
110
+ shape[name] = inputFieldToZod(field).optional();
111
+ }
112
+
113
+ return z.strictObject(shape).partial();
114
+ };
115
+ /** Compile a raw mapping object into expression-aware value mappings. */
116
+ const compileMapping = (
117
+ values?: Record<string, unknown>,
118
+ options?: WorkflowCompileOptions,
119
+ ): CompiledMapping | undefined => {
120
+ if (!values) {
121
+ return undefined;
122
+ }
123
+
124
+ return Object.fromEntries(
125
+ Object.entries(values).map(([key, value]) => [
126
+ key,
127
+ compileValue(value, options),
128
+ ]),
129
+ );
130
+ };
131
+ /** Ensure every task references a provided action implementation. */
132
+ const assertActionsBound = (
133
+ tasks: Record<string, TaskDefinition>,
134
+ actions: Record<string, Action<unknown, unknown, BaseWorkflowContext>>,
135
+ ) => {
136
+ const missing = Object.values(tasks)
137
+ .map((task) => task.action)
138
+ .filter(
139
+ (actionName) =>
140
+ !Object.prototype.hasOwnProperty.call(actions, actionName),
141
+ );
142
+
143
+ if (missing.length > 0) {
144
+ throw new Error(
145
+ `No action implementations provided for: ${missing.join(', ')}`,
146
+ );
147
+ }
148
+ };
149
+ /** Parse settings, compile inputs, and bind actions for each task. */
150
+ const compileTasks = (
151
+ definition: WorkflowDefinition,
152
+ options: WorkflowCompileOptions,
153
+ ): Record<string, CompiledTask> => {
154
+ const compiled: Record<string, CompiledTask> = {};
155
+ const defaultSettings = definition.defaults?.settings;
156
+ const taskDefinitions = extractTaskDefinitionsFromDefs(definition.defs);
157
+ const bindingValidation = validateAndResolveBindings(definition, {
158
+ bindingKinds: options.bindingKinds,
159
+ actions: options.actions,
160
+ });
161
+
162
+ if (bindingValidation.errors.length > 0) {
163
+ throw new Error(
164
+ `Workflow bindings validation failed: ${bindingValidation.errors.join('; ')}`,
165
+ );
166
+ }
167
+
168
+ assertActionsBound(taskDefinitions, options.actions);
169
+
170
+ for (const [taskName, task] of Object.entries(taskDefinitions)) {
171
+ assertSnakeCaseName(taskName, 'action');
172
+
173
+ const action = options.actions[task.action];
174
+ const settingsPayload = mergeSettings(defaultSettings, task.settings);
175
+ const parsedSettings = action.parseSettings(settingsPayload);
176
+
177
+ compiled[taskName] = {
178
+ name: taskName,
179
+ actionName: task.action,
180
+ definition: task,
181
+ action,
182
+ inputs: compileMapping(task.inputs, options) ?? {},
183
+ settings: parsedSettings,
184
+ bindings: mountTaskBindings(
185
+ task.bindings,
186
+ bindingValidation.resolvedDefs,
187
+ options.bindingKinds,
188
+ ),
189
+ };
190
+ }
191
+
192
+ return compiled;
193
+ };
194
+ /** Recursively compile flow steps into executable nodes with generated ids. */
195
+ const compileFlowSteps = (
196
+ steps: FlowStep[],
197
+ path: Array<number | string> = [],
198
+ options?: WorkflowCompileOptions,
199
+ ): CompiledStep[] =>
200
+ steps.map<CompiledStep>((step, index) => {
201
+ const stepPath = [...path, index];
202
+
203
+ if ('do' in step) {
204
+ const label = step.do;
205
+
206
+ return {
207
+ type: StepType.Task,
208
+ id: buildStepId(stepPath, step.do),
209
+ label,
210
+ taskName: step.do,
211
+ };
212
+ }
213
+
214
+ if ('parallel' in step) {
215
+ const label = step.parallel.description ?? 'parallel';
216
+
217
+ return {
218
+ type: StepType.Parallel,
219
+ id: buildStepId(stepPath, 'parallel'),
220
+ label,
221
+ description: step.parallel.description,
222
+ strategy: step.parallel.strategy ?? 'wait_all',
223
+ steps: compileFlowSteps(
224
+ step.parallel.steps,
225
+ [...stepPath, 'parallel'],
226
+ options,
227
+ ),
228
+ } as ParallelStep;
229
+ }
230
+
231
+ if ('conditional' in step) {
232
+ const label = step.conditional.description ?? 'conditional';
233
+ const branches: ConditionalBranch[] = step.conditional.when.map(
234
+ (branch, branchIdx) => ({
235
+ id: buildStepId([...stepPath, 'branch', branchIdx], 'conditional'),
236
+ condition:
237
+ 'condition' in branch
238
+ ? compileValue(branch.condition, options)
239
+ : undefined,
240
+ steps: compileFlowSteps(
241
+ branch.steps,
242
+ [...stepPath, 'branch', branchIdx],
243
+ options,
244
+ ),
245
+ }),
246
+ );
247
+
248
+ return {
249
+ type: StepType.Conditional,
250
+ id: buildStepId(stepPath, 'conditional'),
251
+ label,
252
+ description: step.conditional.description,
253
+ branches,
254
+ };
255
+ }
256
+
257
+ const loop = step.loop;
258
+ const label = loop.name ?? 'loop';
259
+ const commonLoop = {
260
+ type: StepType.Loop,
261
+ id: buildStepId(stepPath, label),
262
+ label,
263
+ name: loop.name,
264
+ description: loop.description,
265
+ accumulate: loop.accumulate
266
+ ? {
267
+ as: loop.accumulate.as,
268
+ initial: loop.accumulate.initial,
269
+ merge: compileValue(loop.accumulate.merge, options),
270
+ }
271
+ : undefined,
272
+ steps: compileFlowSteps(
273
+ loop.steps,
274
+ [...stepPath, loop.name ?? 'loop'],
275
+ options,
276
+ ),
277
+ } as const;
278
+
279
+ if (loop.type === 'for_each') {
280
+ return {
281
+ ...commonLoop,
282
+ loopType: 'for_each',
283
+ forEach: {
284
+ item: loop.for_each.item,
285
+ in: compileValue(loop.for_each.in, options),
286
+ },
287
+ maxConcurrency: loop.max_concurrency,
288
+ until: loop.until ? compileValue(loop.until, options) : undefined,
289
+ } as LoopStep;
290
+ }
291
+
292
+ return {
293
+ ...commonLoop,
294
+ loopType: 'while',
295
+ while: compileValue(loop.while, options),
296
+ } as LoopStep;
297
+ });
298
+
299
+ /** Compile a workflow definition into structures consumable by the runtime. */
300
+ export const compileWorkflow = (
301
+ definition: WorkflowDefinition,
302
+ options: WorkflowCompileOptions,
303
+ ): CompiledWorkflow => {
304
+ const inputParser = buildInputParser(definition.inputs?.schema);
305
+ const tasks = compileTasks(definition, options);
306
+ const flow = compileFlowSteps(definition.flow, [], options);
307
+ const outputMapping = compileMapping(definition.outputs, options) ?? {};
308
+
309
+ return {
310
+ definition,
311
+ tasks,
312
+ flow,
313
+ outputMapping,
314
+ inputParser,
315
+ defaultSettings: definition.defaults?.settings,
316
+ };
317
+ };
@@ -0,0 +1,86 @@
1
+ /*
2
+ * Hexabot — Fair Core License (FCL-1.0-ALv2)
3
+ * Copyright (c) 2025 Hexastack.
4
+ * Full terms: see LICENSE.md.
5
+ */
6
+
7
+ export enum StepType {
8
+ Task = 'task',
9
+ Parallel = 'parallel',
10
+ Conditional = 'conditional',
11
+ Loop = 'loop',
12
+ }
13
+
14
+ export type StepInfo = {
15
+ id: string;
16
+ name: string;
17
+ type: StepType;
18
+ };
19
+
20
+ export type WorkflowEventMap = {
21
+ 'hook:workflow:start': { runId?: string };
22
+ 'hook:workflow:finish': { runId?: string; output: Record<string, unknown> };
23
+ 'hook:workflow:failure': { runId?: string; error: unknown };
24
+ 'hook:workflow:suspended': {
25
+ runId?: string;
26
+ step: StepInfo;
27
+ reason?: string;
28
+ data?: unknown;
29
+ };
30
+ 'hook:step:start': { runId?: string; step: StepInfo };
31
+ 'hook:step:success': { runId?: string; step: StepInfo };
32
+ 'hook:step:error': { runId?: string; step: StepInfo; error: unknown };
33
+ 'hook:step:suspended': {
34
+ runId?: string;
35
+ step: StepInfo;
36
+ reason?: string;
37
+ data?: unknown;
38
+ };
39
+ 'hook:step:skipped': { runId?: string; step: StepInfo; reason?: string };
40
+ };
41
+
42
+ export type EventEmitterLike = {
43
+ emit(event: string | symbol, ...args: any[]): unknown;
44
+ on(event: string | symbol, listener: (...args: any[]) => void): unknown;
45
+ };
46
+
47
+ export type WorkflowEventEmitterLike<E = unknown> = E & EventEmitterLike;
48
+
49
+ type EventKey = keyof WorkflowEventMap;
50
+ type AnyListener = (payload: WorkflowEventMap[EventKey]) => void;
51
+
52
+ /**
53
+ * Minimal, browser-friendly event emitter that preserves the typed payloads
54
+ * exposed by {@link WorkflowEventMap}. It supports the subset of the Node.js
55
+ * EventEmitter API that the runtime relies on (`emit` and `on`).
56
+ */
57
+ export class WorkflowEventEmitter
58
+ implements WorkflowEventEmitterLike<WorkflowEventEmitter>
59
+ {
60
+ private listeners = new Map<EventKey, Set<AnyListener>>();
61
+
62
+ emit<K extends keyof WorkflowEventMap>(
63
+ event: K,
64
+ payload: WorkflowEventMap[K],
65
+ ): boolean {
66
+ const eventListeners = this.listeners.get(event);
67
+ if (!eventListeners || eventListeners.size === 0) {
68
+ return false;
69
+ }
70
+
71
+ eventListeners.forEach((listener) => listener(payload));
72
+
73
+ return true;
74
+ }
75
+
76
+ on<K extends keyof WorkflowEventMap>(
77
+ event: K,
78
+ listener: (payload: WorkflowEventMap[K]) => void,
79
+ ): this {
80
+ const listeners = this.listeners.get(event) ?? new Set<AnyListener>();
81
+ listeners.add(listener as AnyListener);
82
+ this.listeners.set(event, listeners);
83
+
84
+ return this;
85
+ }
86
+ }