@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,83 @@
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 { BaseWorkflowContext } from '../context';
10
+
11
+ import { AbstractAction } from './abstract-action';
12
+ import type {
13
+ Action,
14
+ ActionExecutionArgs,
15
+ ActionMetadata,
16
+ AnyRuntimeBindings,
17
+ } from './action.types';
18
+
19
+ export type DefineActionParams<
20
+ I,
21
+ O,
22
+ Ctx extends BaseWorkflowContext,
23
+ S,
24
+ B extends AnyRuntimeBindings = AnyRuntimeBindings,
25
+ > = {
26
+ name: string;
27
+ description?: string;
28
+ supportedBindings?: readonly string[];
29
+ inputSchema?: ZodType<I>;
30
+ outputSchema?: ZodType<O>;
31
+ settingSchema?: ZodType<S>;
32
+ execute: (args: ActionExecutionArgs<I, Ctx, S, B>) => Promise<O> | O;
33
+ };
34
+
35
+ /**
36
+ * Builds an {@link AbstractAction} subclass from simple configuration.
37
+ *
38
+ * @param params - Action definition containing metadata and runtime logic.
39
+ * @returns Instantiated action ready to be used by a workflow.
40
+ * @typeParam I - Action input type.
41
+ * @typeParam O - Action output type.
42
+ * @typeParam Ctx - Workflow context type.
43
+ */
44
+ export function defineAction<
45
+ I,
46
+ O,
47
+ Ctx extends BaseWorkflowContext,
48
+ S,
49
+ B extends AnyRuntimeBindings = AnyRuntimeBindings,
50
+ >(params: DefineActionParams<I, O, Ctx, S, B>): Action<I, O, Ctx, S, B> {
51
+ type ActionArgs = ActionExecutionArgs<I, Ctx, S, B>;
52
+
53
+ const defaultSettingsSchema = z.any() as ZodType<S>;
54
+ const defaultInputSchema = z.any() as ZodType<I>;
55
+ const defaultOutputSchema = z.any() as ZodType<O>;
56
+
57
+ class FnAction extends AbstractAction<I, O, Ctx, S, B> {
58
+ constructor() {
59
+ const metadata: ActionMetadata<I, O, S> = {
60
+ name: params.name,
61
+ description: params.description ?? '',
62
+ inputSchema: params.inputSchema ?? defaultInputSchema,
63
+ outputSchema: params.outputSchema ?? defaultOutputSchema,
64
+ settingsSchema: params.settingSchema ?? defaultSettingsSchema,
65
+ supportedBindings: params.supportedBindings,
66
+ };
67
+
68
+ super(metadata);
69
+ }
70
+
71
+ /**
72
+ * Delegates to the user supplied execute callback.
73
+ *
74
+ * @param args - Action execution arguments supplied by the runner.
75
+ * @returns Result of the user callback as a promise.
76
+ */
77
+ async execute(args: ActionArgs): Promise<O> {
78
+ return await Promise.resolve(params.execute(args));
79
+ }
80
+ }
81
+
82
+ return new FnAction();
83
+ }
@@ -0,0 +1,93 @@
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 { ZodType } from 'zod';
8
+
9
+ import type { InferWorkflowBindings } from '../bindings/base-binding';
10
+ import { BaseWorkflowContext } from '../context';
11
+ import { Settings } from '../dsl.types';
12
+ import { Deferred } from '../utils/deferred';
13
+
14
+ export type RuntimeSettings<S = unknown> = Settings & S;
15
+
16
+ export type AnyRuntimeBindings = InferWorkflowBindings;
17
+
18
+ export interface ActionMetadata<I, O, S> {
19
+ name: string;
20
+ description: string;
21
+ inputSchema: ZodType<I>;
22
+ outputSchema: ZodType<O>;
23
+ settingsSchema: ZodType<S>;
24
+ supportedBindings?: readonly string[];
25
+ }
26
+
27
+ export interface ActionExecutionArgs<
28
+ I,
29
+ C extends BaseWorkflowContext = BaseWorkflowContext,
30
+ S = unknown,
31
+ B extends AnyRuntimeBindings = AnyRuntimeBindings,
32
+ > {
33
+ input: I;
34
+ context: C;
35
+ settings: RuntimeSettings<S>;
36
+ bindings: B;
37
+ }
38
+
39
+ export interface Action<
40
+ I = unknown,
41
+ O = unknown,
42
+ C extends BaseWorkflowContext = BaseWorkflowContext,
43
+ S = unknown,
44
+ B extends AnyRuntimeBindings = AnyRuntimeBindings,
45
+ > {
46
+ readonly name: string;
47
+ readonly description: string;
48
+ readonly inputSchema: ZodType<I>;
49
+ readonly outputSchema: ZodType<O>;
50
+ readonly settingSchema?: ZodType<S>;
51
+ readonly supportedBindings?: readonly string[];
52
+
53
+ execute(args: ActionExecutionArgs<I, C, S, B>): Promise<O>;
54
+ parseInput(payload: unknown): I;
55
+ parseOutput(payload: unknown): O;
56
+ parseSettings(payload: unknown): RuntimeSettings<S>;
57
+ run(
58
+ payload: unknown,
59
+ context: C,
60
+ settings?: Partial<RuntimeSettings<S>>,
61
+ bindings?: B,
62
+ ): Promise<O>;
63
+ }
64
+
65
+ export type Actions = Record<string, Action>;
66
+
67
+ export type InferActionArgs<A extends Action> = Parameters<A['execute']>[0];
68
+
69
+ export type InferActionInput<A extends Action> = InferActionArgs<A>['input'];
70
+
71
+ export type InferActionContext<A extends Action> =
72
+ InferActionArgs<A>['context'];
73
+
74
+ export type InferActionOutput<A extends Action> = Awaited<
75
+ ReturnType<A['execute']>
76
+ >;
77
+
78
+ export type InferActionSettings<S extends Action> =
79
+ InferActionArgs<S>['settings'];
80
+
81
+ export type InferActionBindings<S extends Action> =
82
+ InferActionArgs<S>['bindings'];
83
+
84
+ export interface SuspensionNotice {
85
+ stepId: string;
86
+ reason?: string;
87
+ data?: unknown;
88
+ resume: Deferred<unknown>;
89
+ }
90
+
91
+ export type ActionExecutionOutcome<T> =
92
+ | { type: 'completed'; value: T }
93
+ | { type: 'suspended'; notice: SuspensionNotice };
@@ -0,0 +1,508 @@
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, type ZodIssue } from 'zod';
8
+
9
+ const TASK_KIND = 'task';
10
+
11
+ export type BindingKindSchema = z.ZodTypeAny;
12
+
13
+ export type BindingActionPolicy = 'forbidden' | 'optional' | 'required';
14
+
15
+ export type BindingValidationActionMetadata = {
16
+ supportedBindings?: readonly string[];
17
+ };
18
+
19
+ export type BindingKindDescriptor<
20
+ TSchema extends z.ZodTypeAny = BindingKindSchema,
21
+ TMultiple extends boolean = boolean,
22
+ > = {
23
+ schema: TSchema;
24
+ multiple: TMultiple;
25
+ supportedBindings?: readonly string[];
26
+ actionPolicy?: BindingActionPolicy;
27
+ };
28
+
29
+ export type BindingKindSchemas = Record<string, BindingKindDescriptor>;
30
+
31
+ export type MountedBindingPayload<
32
+ TBindingKind extends BindingKindDescriptor = BindingKindDescriptor,
33
+ TBindingKinds extends BindingKindSchemas = BindingKindSchemas,
34
+ > = {
35
+ settings: z.infer<TBindingKind['schema']>;
36
+ action?: string;
37
+ bindings?: InferWorkflowBindings<TBindingKinds>;
38
+ };
39
+
40
+ export type InferMountedBindingValue<
41
+ TBindingKind extends BindingKindDescriptor = BindingKindDescriptor,
42
+ TBindingKinds extends BindingKindSchemas = BindingKindSchemas,
43
+ > = TBindingKind['multiple'] extends true
44
+ ? Record<string, MountedBindingPayload<TBindingKind, TBindingKinds>>
45
+ : TBindingKind['multiple'] extends false
46
+ ? MountedBindingPayload<TBindingKind, TBindingKinds>
47
+ :
48
+ | MountedBindingPayload<TBindingKind, TBindingKinds>
49
+ | Record<string, MountedBindingPayload<TBindingKind, TBindingKinds>>;
50
+
51
+ export type InferWorkflowBindings<
52
+ TBindingKinds extends BindingKindSchemas = BindingKindSchemas,
53
+ > = Partial<{
54
+ [K in keyof TBindingKinds & string]: InferMountedBindingValue<
55
+ TBindingKinds[K],
56
+ TBindingKinds
57
+ >;
58
+ }>;
59
+
60
+ export type CompiledTaskBindings = InferWorkflowBindings;
61
+
62
+ export type TaskBindingReferences = Record<string, string | string[]>;
63
+
64
+ export type DefLike = {
65
+ kind: string;
66
+ description?: string;
67
+ action?: string;
68
+ settings?: unknown;
69
+ bindings?: TaskBindingReferences;
70
+ [key: string]: unknown;
71
+ };
72
+
73
+ export type BindingAwareWorkflowLike = {
74
+ defs: Record<string, DefLike>;
75
+ };
76
+
77
+ export type ResolvedBindingDef = {
78
+ kind: string;
79
+ payload: MountedBindingPayload;
80
+ };
81
+
82
+ export type ResolvedBindingDefs = Record<string, ResolvedBindingDef>;
83
+
84
+ export type ValidateAndResolveBindingsOptions = {
85
+ bindingKinds?: BindingKindSchemas;
86
+ actions?: Record<string, BindingValidationActionMetadata>;
87
+ };
88
+
89
+ type BindingValidationResult = {
90
+ errors: string[];
91
+ resolvedDefs: ResolvedBindingDefs;
92
+ };
93
+
94
+ const resolveValidationOptions = (
95
+ options?: BindingKindSchemas | ValidateAndResolveBindingsOptions,
96
+ ): ValidateAndResolveBindingsOptions => {
97
+ if (!options) {
98
+ return {};
99
+ }
100
+
101
+ if ('bindingKinds' in options || 'actions' in options) {
102
+ return options as ValidateAndResolveBindingsOptions;
103
+ }
104
+
105
+ return { bindingKinds: options as BindingKindSchemas };
106
+ };
107
+ const hasBindingsConfigured = (workflow: BindingAwareWorkflowLike): boolean => {
108
+ const defs = workflow.defs ?? {};
109
+ const hasNonTaskDefs = Object.values(defs).some(
110
+ (definition) => definition.kind !== TASK_KIND,
111
+ );
112
+ const hasNestedBindings = Object.values(defs).some(
113
+ (definition) =>
114
+ definition.bindings && Object.keys(definition.bindings).length > 0,
115
+ );
116
+
117
+ return hasNonTaskDefs || hasNestedBindings;
118
+ };
119
+ const formatZodIssues = (issues: ZodIssue[], prefix: string): string[] =>
120
+ issues.map((issue) => {
121
+ const path = issue.path.join('.') || '<root>';
122
+
123
+ return `${prefix}.${path}: ${issue.message}`;
124
+ });
125
+ const collectDuplicateReferences = (refs: string[]): string[] => {
126
+ const seen = new Set<string>();
127
+ const duplicates = new Set<string>();
128
+
129
+ for (const ref of refs) {
130
+ if (seen.has(ref)) {
131
+ duplicates.add(ref);
132
+ continue;
133
+ }
134
+
135
+ seen.add(ref);
136
+ }
137
+
138
+ return Array.from(duplicates).sort();
139
+ };
140
+ const toBindingRefs = (refs: string | string[]): string[] =>
141
+ Array.isArray(refs) ? refs : [refs];
142
+ const collectBindingRefs = (
143
+ bindings?: TaskBindingReferences,
144
+ ): Array<{ kind: string; ref: string }> => {
145
+ if (!bindings) {
146
+ return [];
147
+ }
148
+
149
+ const refs: Array<{ kind: string; ref: string }> = [];
150
+ for (const [bindingKind, bindingRefs] of Object.entries(bindings)) {
151
+ for (const ref of toBindingRefs(bindingRefs)) {
152
+ refs.push({ kind: bindingKind, ref });
153
+ }
154
+ }
155
+
156
+ return refs;
157
+ };
158
+ const detectBindingCycles = (defs: Record<string, DefLike>): string[] => {
159
+ const errors: string[] = [];
160
+ const reportedCycles = new Set<string>();
161
+ const states = new Map<string, 'visiting' | 'visited'>();
162
+ const stack: string[] = [];
163
+ const defNames = Object.keys(defs);
164
+ const visit = (defName: string) => {
165
+ const state = states.get(defName);
166
+ if (state === 'visited') {
167
+ return;
168
+ }
169
+
170
+ if (state === 'visiting') {
171
+ const cycleStart = stack.indexOf(defName);
172
+ if (cycleStart === -1) {
173
+ return;
174
+ }
175
+
176
+ const cyclePath = [...stack.slice(cycleStart), defName];
177
+ const cycleKey = cyclePath.join('->');
178
+
179
+ if (!reportedCycles.has(cycleKey)) {
180
+ reportedCycles.add(cycleKey);
181
+ errors.push(
182
+ `defs.${defName}.bindings: Circular binding reference detected (${cyclePath.join(' -> ')}).`,
183
+ );
184
+ }
185
+
186
+ return;
187
+ }
188
+
189
+ states.set(defName, 'visiting');
190
+ stack.push(defName);
191
+
192
+ const definition = defs[defName];
193
+ for (const { ref } of collectBindingRefs(definition?.bindings)) {
194
+ if (!Object.prototype.hasOwnProperty.call(defs, ref)) {
195
+ continue;
196
+ }
197
+
198
+ visit(ref);
199
+ }
200
+
201
+ stack.pop();
202
+ states.set(defName, 'visited');
203
+ };
204
+
205
+ for (const defName of defNames) {
206
+ visit(defName);
207
+ }
208
+
209
+ return errors;
210
+ };
211
+ const resolveSupportedBindingKinds = (
212
+ definition: DefLike,
213
+ defName: string,
214
+ kinds: BindingKindSchemas,
215
+ actions: Record<string, BindingValidationActionMetadata> | undefined,
216
+ errors: string[],
217
+ ): readonly string[] | null => {
218
+ if (definition.action) {
219
+ if (!actions) {
220
+ return null;
221
+ }
222
+
223
+ const action = actions[definition.action];
224
+ if (!action) {
225
+ errors.push(
226
+ `defs.${defName}.action: No action implementation provided for "${definition.action}".`,
227
+ );
228
+
229
+ return [];
230
+ }
231
+
232
+ return action.supportedBindings ?? [];
233
+ }
234
+
235
+ if (definition.kind === TASK_KIND) {
236
+ return [];
237
+ }
238
+
239
+ return kinds[definition.kind]?.supportedBindings ?? [];
240
+ };
241
+
242
+ export const validateAndResolveBindings = (
243
+ workflow: BindingAwareWorkflowLike,
244
+ options?: BindingKindSchemas | ValidateAndResolveBindingsOptions,
245
+ ): BindingValidationResult => {
246
+ const errors: string[] = [];
247
+ const resolvedDefs: ResolvedBindingDefs = {};
248
+ const defs = workflow.defs ?? {};
249
+ const resolvedOptions = resolveValidationOptions(options);
250
+ const kinds = resolvedOptions.bindingKinds ?? {};
251
+ const actions = resolvedOptions.actions;
252
+ const kindNames = Object.keys(kinds);
253
+ const hasBindingUsage = hasBindingsConfigured(workflow);
254
+ const parsedSettingsByDefName = new Map<string, unknown>();
255
+
256
+ if (hasBindingUsage && kindNames.length === 0) {
257
+ errors.push(
258
+ 'Workflows that declare non-task defs or nested bindings require a non-empty "bindingKinds" registry.',
259
+ );
260
+ }
261
+
262
+ for (const [defName, defDefinition] of Object.entries(defs)) {
263
+ if (defDefinition.kind === TASK_KIND) {
264
+ if (defDefinition.action && actions && !actions[defDefinition.action]) {
265
+ errors.push(
266
+ `defs.${defName}.action: No action implementation provided for "${defDefinition.action}".`,
267
+ );
268
+ }
269
+ continue;
270
+ }
271
+
272
+ const kindDefinition = kinds[defDefinition.kind];
273
+
274
+ if (!kindDefinition) {
275
+ errors.push(
276
+ `defs.${defName}.kind: Unknown binding kind "${defDefinition.kind}".`,
277
+ );
278
+ continue;
279
+ }
280
+
281
+ const actionPolicy = kindDefinition.actionPolicy ?? 'optional';
282
+ if (actionPolicy === 'required' && !defDefinition.action) {
283
+ errors.push(
284
+ `defs.${defName}.action: Binding kind "${defDefinition.kind}" requires an action.`,
285
+ );
286
+ }
287
+ if (actionPolicy === 'forbidden' && defDefinition.action) {
288
+ errors.push(
289
+ `defs.${defName}.action: Binding kind "${defDefinition.kind}" does not allow action declarations.`,
290
+ );
291
+ }
292
+ if (defDefinition.action && actions && !actions[defDefinition.action]) {
293
+ errors.push(
294
+ `defs.${defName}.action: No action implementation provided for "${defDefinition.action}".`,
295
+ );
296
+ }
297
+
298
+ const { schema } = kindDefinition;
299
+ const parsedPayload = schema.safeParse(defDefinition.settings);
300
+
301
+ if (!parsedPayload.success) {
302
+ errors.push(
303
+ ...formatZodIssues(
304
+ parsedPayload.error.issues,
305
+ `defs.${defName}.settings`,
306
+ ),
307
+ );
308
+ continue;
309
+ }
310
+
311
+ parsedSettingsByDefName.set(defName, parsedPayload.data);
312
+ }
313
+
314
+ for (const [defName, defDefinition] of Object.entries(defs)) {
315
+ const defBindings = defDefinition.bindings;
316
+
317
+ if (!defBindings) {
318
+ continue;
319
+ }
320
+
321
+ const supportedKinds = resolveSupportedBindingKinds(
322
+ defDefinition,
323
+ defName,
324
+ kinds,
325
+ actions,
326
+ errors,
327
+ );
328
+ const shouldValidateSupportedKinds = Array.isArray(supportedKinds);
329
+
330
+ for (const [bindingKind, bindingRefs] of Object.entries(defBindings)) {
331
+ const kindDefinition = kinds[bindingKind];
332
+
333
+ if (!kindDefinition) {
334
+ errors.push(
335
+ `defs.${defName}.bindings.${bindingKind}: Unknown binding kind "${bindingKind}".`,
336
+ );
337
+ continue;
338
+ }
339
+
340
+ const { multiple } = kindDefinition;
341
+ if (multiple && !Array.isArray(bindingRefs)) {
342
+ errors.push(
343
+ `defs.${defName}.bindings.${bindingKind}: Expected an array of def references for binding kind "${bindingKind}".`,
344
+ );
345
+ continue;
346
+ }
347
+ if (!multiple && typeof bindingRefs !== 'string') {
348
+ errors.push(
349
+ `defs.${defName}.bindings.${bindingKind}: Expected a single def reference string for binding kind "${bindingKind}".`,
350
+ );
351
+ continue;
352
+ }
353
+
354
+ if (
355
+ shouldValidateSupportedKinds &&
356
+ supportedKinds &&
357
+ !supportedKinds.includes(bindingKind)
358
+ ) {
359
+ const supportedKindsLabel =
360
+ supportedKinds.length > 0 ? supportedKinds.join(', ') : '<none>';
361
+ errors.push(
362
+ `defs.${defName}.bindings.${bindingKind}: "${defDefinition.action ?? defDefinition.kind}" does not support binding kind "${bindingKind}". Supported binding kinds: ${supportedKindsLabel}.`,
363
+ );
364
+ }
365
+
366
+ const refs = toBindingRefs(bindingRefs);
367
+ const duplicateRefs = collectDuplicateReferences(refs);
368
+ if (duplicateRefs.length > 0) {
369
+ errors.push(
370
+ `defs.${defName}.bindings.${bindingKind}: Duplicate def reference(s): ${duplicateRefs.join(', ')}`,
371
+ );
372
+ }
373
+
374
+ for (const ref of refs) {
375
+ const definition = defs[ref];
376
+
377
+ if (!definition) {
378
+ errors.push(
379
+ `defs.${defName}.bindings.${bindingKind}: Unknown def reference "${ref}".`,
380
+ );
381
+ continue;
382
+ }
383
+
384
+ if (definition.kind !== bindingKind) {
385
+ errors.push(
386
+ `defs.${defName}.bindings.${bindingKind}: Def "${ref}" has kind "${definition.kind}" and cannot be mounted as "${bindingKind}".`,
387
+ );
388
+ }
389
+ }
390
+ }
391
+ }
392
+
393
+ errors.push(...detectBindingCycles(defs));
394
+
395
+ const inProgress = new Set<string>();
396
+ const mountResolvedDef = (
397
+ defName: string,
398
+ ): MountedBindingPayload | undefined => {
399
+ const existing = resolvedDefs[defName];
400
+ if (existing) {
401
+ return existing.payload;
402
+ }
403
+
404
+ const definition = defs[defName];
405
+ if (!definition || definition.kind === TASK_KIND) {
406
+ return undefined;
407
+ }
408
+
409
+ const parsedSettings = parsedSettingsByDefName.get(defName);
410
+ if (parsedSettings === undefined) {
411
+ return undefined;
412
+ }
413
+
414
+ if (inProgress.has(defName)) {
415
+ return undefined;
416
+ }
417
+
418
+ inProgress.add(defName);
419
+ const nestedBindings = mountTaskBindings(
420
+ definition.bindings,
421
+ resolvedDefs,
422
+ kinds,
423
+ mountResolvedDef,
424
+ );
425
+ const payload: MountedBindingPayload = {
426
+ settings: parsedSettings,
427
+ ...(definition.action ? { action: definition.action } : {}),
428
+ ...(Object.keys(nestedBindings).length > 0
429
+ ? { bindings: nestedBindings }
430
+ : {}),
431
+ };
432
+
433
+ resolvedDefs[defName] = {
434
+ kind: definition.kind,
435
+ payload,
436
+ };
437
+ inProgress.delete(defName);
438
+
439
+ return payload;
440
+ };
441
+
442
+ for (const [defName, definition] of Object.entries(defs)) {
443
+ if (definition.kind === TASK_KIND) {
444
+ continue;
445
+ }
446
+
447
+ mountResolvedDef(defName);
448
+ }
449
+
450
+ return { errors, resolvedDefs };
451
+ };
452
+
453
+ export const mountTaskBindings = (
454
+ taskBindings: TaskBindingReferences | undefined,
455
+ resolvedDefs: ResolvedBindingDefs,
456
+ bindingKinds?: BindingKindSchemas,
457
+ resolveDef?: (defName: string) => MountedBindingPayload | undefined,
458
+ ): CompiledTaskBindings => {
459
+ if (!taskBindings) {
460
+ return {};
461
+ }
462
+
463
+ const mounted: CompiledTaskBindings = {};
464
+ const kinds = bindingKinds ?? {};
465
+
466
+ for (const [bindingKind, bindingRefs] of Object.entries(taskBindings)) {
467
+ const refs = toBindingRefs(bindingRefs);
468
+ const isMultiple =
469
+ kinds[bindingKind]?.multiple ?? Array.isArray(bindingRefs);
470
+
471
+ if (!isMultiple) {
472
+ const ref = refs[0];
473
+
474
+ if (!ref) {
475
+ continue;
476
+ }
477
+
478
+ const resolvedDefPayload =
479
+ resolvedDefs[ref]?.payload ?? resolveDef?.(ref) ?? undefined;
480
+
481
+ if (!resolvedDefPayload) {
482
+ continue;
483
+ }
484
+
485
+ mounted[bindingKind] = resolvedDefPayload;
486
+ continue;
487
+ }
488
+
489
+ const mountedKindDefs: Record<string, MountedBindingPayload> = {};
490
+
491
+ for (const ref of refs) {
492
+ const resolvedDefPayload =
493
+ resolvedDefs[ref]?.payload ?? resolveDef?.(ref) ?? undefined;
494
+
495
+ if (!resolvedDefPayload) {
496
+ continue;
497
+ }
498
+
499
+ mountedKindDefs[ref] = resolvedDefPayload;
500
+ }
501
+
502
+ if (Object.keys(mountedKindDefs).length > 0) {
503
+ mounted[bindingKind] = mountedKindDefs;
504
+ }
505
+ }
506
+
507
+ return mounted;
508
+ };