@memberjunction/ai-agent-manager 4.2.0 → 4.3.1

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.
@@ -1,4 +1,4 @@
1
1
 
2
- > @memberjunction/ai-agent-manager@4.2.0 build
2
+ > @memberjunction/ai-agent-manager@4.3.1 build
3
3
  > tsc && tsc-alias -f
4
4
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # @memberjunction/ai-agent-manager
2
2
 
3
+ ## 4.3.1
4
+
5
+ ### Patch Changes
6
+
7
+ - @memberjunction/ai-agents@4.3.1
8
+ - @memberjunction/ai-engine-base@4.3.1
9
+ - @memberjunction/ai-core-plus@4.3.1
10
+ - @memberjunction/aiengine@4.3.1
11
+ - @memberjunction/core@4.3.1
12
+ - @memberjunction/core-entities@4.3.1
13
+ - @memberjunction/global@4.3.1
14
+ - @memberjunction/templates@4.3.1
15
+
16
+ ## 4.3.0
17
+
18
+ ### Patch Changes
19
+
20
+ - Updated dependencies [6f4d33f]
21
+ - Updated dependencies [564e1af]
22
+ - @memberjunction/ai-agents@4.3.0
23
+ - @memberjunction/core@4.3.0
24
+ - @memberjunction/core-entities@4.3.0
25
+ - @memberjunction/ai-engine-base@4.3.0
26
+ - @memberjunction/ai-core-plus@4.3.0
27
+ - @memberjunction/aiengine@4.3.0
28
+ - @memberjunction/templates@4.3.0
29
+ - @memberjunction/global@4.3.0
30
+
3
31
  ## 4.2.0
4
32
 
5
33
  ### Patch Changes
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@memberjunction/ai-agent-manager",
3
3
  "type": "module",
4
- "version": "4.2.0",
4
+ "version": "4.3.1",
5
5
  "description": "Core interfaces and types for the MemberJunction Agent Manager system",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -9,7 +9,9 @@
9
9
  "build": "tsc && tsc-alias -f",
10
10
  "watch": "tsc -w",
11
11
  "clean": "rimraf dist",
12
- "prepublishOnly": "npm run build"
12
+ "prepublishOnly": "npm run build",
13
+ "test": "vitest run",
14
+ "test:watch": "vitest"
13
15
  },
14
16
  "keywords": [
15
17
  "memberjunction",
@@ -20,14 +22,14 @@
20
22
  "author": "MemberJunction",
21
23
  "license": "ISC",
22
24
  "dependencies": {
23
- "@memberjunction/ai-agents": "4.2.0",
24
- "@memberjunction/ai-core-plus": "4.2.0",
25
- "@memberjunction/ai-engine-base": "4.2.0",
26
- "@memberjunction/aiengine": "4.2.0",
27
- "@memberjunction/core": "4.2.0",
28
- "@memberjunction/core-entities": "4.2.0",
29
- "@memberjunction/global": "4.2.0",
30
- "@memberjunction/templates": "4.2.0"
25
+ "@memberjunction/ai-agents": "4.3.1",
26
+ "@memberjunction/ai-core-plus": "4.3.1",
27
+ "@memberjunction/ai-engine-base": "4.3.1",
28
+ "@memberjunction/aiengine": "4.3.1",
29
+ "@memberjunction/core": "4.3.1",
30
+ "@memberjunction/core-entities": "4.3.1",
31
+ "@memberjunction/global": "4.3.1",
32
+ "@memberjunction/templates": "4.3.1"
31
33
  },
32
34
  "devDependencies": {
33
35
  "@types/node": "^24.10.11",
@@ -0,0 +1,323 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
2
+
3
+ vi.mock('@memberjunction/core', () => ({
4
+ Metadata: vi.fn().mockImplementation(() => ({
5
+ GetEntityObject: vi.fn().mockResolvedValue({
6
+ Load: vi.fn().mockResolvedValue(true),
7
+ Save: vi.fn().mockResolvedValue(true),
8
+ Delete: vi.fn().mockResolvedValue(true),
9
+ NewRecord: vi.fn(),
10
+ Validate: vi.fn().mockReturnValue({ Success: true, Errors: [] }),
11
+ ID: 'saved-id-1',
12
+ Name: '',
13
+ Description: null,
14
+ IconClass: null,
15
+ LogoURL: null,
16
+ ParentID: null,
17
+ DriverClass: null,
18
+ ModelSelectionMode: 'Agent Type',
19
+ TypeID: null,
20
+ Status: 'Active',
21
+ PayloadDownstreamPaths: null,
22
+ PayloadUpstreamPaths: null,
23
+ PayloadSelfReadPaths: null,
24
+ PayloadSelfWritePaths: null,
25
+ PayloadScope: null,
26
+ FinalPayloadValidation: null,
27
+ FinalPayloadValidationMode: 'Retry',
28
+ FinalPayloadValidationMaxRetries: 3,
29
+ StartingPayloadValidation: null,
30
+ StartingPayloadValidationMode: 'Fail',
31
+ MaxCostPerRun: null,
32
+ MaxTokensPerRun: null,
33
+ MaxIterationsPerRun: null,
34
+ MaxTimePerRun: null,
35
+ MinExecutionsPerRun: null,
36
+ MaxExecutionsPerRun: null,
37
+ DefaultPromptEffortLevel: null,
38
+ ChatHandlingOption: null,
39
+ DefaultArtifactTypeID: null,
40
+ OwnerUserID: null,
41
+ InvocationMode: 'Any',
42
+ FunctionalRequirements: null,
43
+ TechnicalDesign: null,
44
+ }),
45
+ })),
46
+ RunView: vi.fn().mockImplementation(() => ({
47
+ RunView: vi.fn().mockResolvedValue({
48
+ Success: true,
49
+ Results: [],
50
+ RowCount: 0,
51
+ }),
52
+ RunViews: vi.fn().mockResolvedValue([
53
+ { Success: true, Results: [], RowCount: 0 },
54
+ { Success: true, Results: [], RowCount: 0 },
55
+ { Success: true, Results: [], RowCount: 0 },
56
+ { Success: true, Results: [], RowCount: 0 },
57
+ { Success: true, Results: [], RowCount: 0 },
58
+ ]),
59
+ })),
60
+ UserInfo: vi.fn(),
61
+ LogError: vi.fn(),
62
+ }));
63
+
64
+ vi.mock('@memberjunction/core-entities', () => ({
65
+ AIAgentEntity: vi.fn(),
66
+ AIAgentActionEntity: vi.fn(),
67
+ AIAgentRelationshipEntity: vi.fn(),
68
+ AIAgentStepEntity: vi.fn(),
69
+ AIAgentStepPathEntity: vi.fn(),
70
+ }));
71
+
72
+ vi.mock('@memberjunction/ai-core-plus', () => ({
73
+ AgentSpec: vi.fn(),
74
+ AgentActionSpec: vi.fn(),
75
+ SubAgentSpec: vi.fn(),
76
+ }));
77
+
78
+ import { AgentSpecSync } from '../agent-spec-sync';
79
+
80
+ describe('AgentSpecSync', () => {
81
+ const mockUser = { ID: 'user-1', Email: 'test@test.com' } as never;
82
+
83
+ beforeEach(() => {
84
+ vi.clearAllMocks();
85
+ });
86
+
87
+ describe('constructor', () => {
88
+ it('should create empty spec when no args provided', () => {
89
+ const sync = new AgentSpecSync();
90
+ expect(sync.spec).toBeDefined();
91
+ expect(sync.spec.ID).toBe('');
92
+ expect(sync.spec.Name).toBe('');
93
+ expect(sync.spec.Actions).toEqual([]);
94
+ expect(sync.spec.SubAgents).toEqual([]);
95
+ });
96
+
97
+ it('should initialize with partial spec', () => {
98
+ const sync = new AgentSpecSync({
99
+ Name: 'Test Agent',
100
+ Description: 'A test agent',
101
+ });
102
+ expect(sync.spec.Name).toBe('Test Agent');
103
+ expect(sync.spec.Description).toBe('A test agent');
104
+ expect(sync.isDirty).toBe(true);
105
+ });
106
+
107
+ it('should store context user', () => {
108
+ const sync = new AgentSpecSync({ Name: 'Test' }, mockUser);
109
+ expect(sync.isDirty).toBe(true);
110
+ });
111
+ });
112
+
113
+ describe('isDirty / isLoaded', () => {
114
+ it('should be dirty when created with spec', () => {
115
+ const sync = new AgentSpecSync({ Name: 'Test' });
116
+ expect(sync.isDirty).toBe(true);
117
+ expect(sync.isLoaded).toBe(false);
118
+ });
119
+
120
+ it('should not be dirty when created empty', () => {
121
+ const sync = new AgentSpecSync();
122
+ expect(sync.isDirty).toBe(false);
123
+ });
124
+ });
125
+
126
+ describe('markDirty', () => {
127
+ it('should set dirty flag', () => {
128
+ const sync = new AgentSpecSync();
129
+ expect(sync.isDirty).toBe(false);
130
+ sync.markDirty();
131
+ expect(sync.isDirty).toBe(true);
132
+ });
133
+ });
134
+
135
+ describe('markLoaded', () => {
136
+ it('should set loaded flag', () => {
137
+ const sync = new AgentSpecSync();
138
+ expect(sync.isLoaded).toBe(false);
139
+ sync.markLoaded();
140
+ expect(sync.isLoaded).toBe(true);
141
+ });
142
+ });
143
+
144
+ describe('toJSON', () => {
145
+ it('should return a copy of the spec', () => {
146
+ const sync = new AgentSpecSync({
147
+ Name: 'Test Agent',
148
+ Description: 'Description',
149
+ });
150
+ const json = sync.toJSON();
151
+ expect(json.Name).toBe('Test Agent');
152
+ expect(json.Description).toBe('Description');
153
+ });
154
+
155
+ it('should return a new object (not the same reference)', () => {
156
+ const sync = new AgentSpecSync({ Name: 'Test' });
157
+ const json = sync.toJSON();
158
+ json.Name = 'Modified';
159
+ expect(sync.spec.Name).toBe('Test');
160
+ });
161
+ });
162
+
163
+ describe('FromRawSpec', () => {
164
+ it('should create instance from raw spec', () => {
165
+ const rawSpec = {
166
+ ID: 'raw-1',
167
+ Name: 'Raw Agent',
168
+ StartingPayloadValidationMode: 'Fail' as const,
169
+ Actions: [],
170
+ SubAgents: [],
171
+ };
172
+ const sync = AgentSpecSync.FromRawSpec(rawSpec, mockUser);
173
+ expect(sync.spec.Name).toBe('Raw Agent');
174
+ expect(sync.isDirty).toBe(true);
175
+ });
176
+ });
177
+
178
+ describe('mutation tracking', () => {
179
+ it('should start with no mutations', () => {
180
+ const sync = new AgentSpecSync({ Name: 'Test' });
181
+ expect(sync.getMutations()).toEqual([]);
182
+ });
183
+
184
+ it('should clear mutations', () => {
185
+ const sync = new AgentSpecSync({ Name: 'Test' });
186
+ sync.clearMutations();
187
+ expect(sync.getMutations()).toEqual([]);
188
+ });
189
+ });
190
+
191
+ describe('initializeSpec defaults', () => {
192
+ it('should set default StartingPayloadValidationMode to Fail', () => {
193
+ const sync = new AgentSpecSync({ Name: 'Test' });
194
+ expect(sync.spec.StartingPayloadValidationMode).toBe('Fail');
195
+ });
196
+
197
+ it('should set default FinalPayloadValidationMode to Retry', () => {
198
+ const sync = new AgentSpecSync({ Name: 'Test' });
199
+ expect(sync.spec.FinalPayloadValidationMode).toBe('Retry');
200
+ });
201
+
202
+ it('should set default InvocationMode to Any', () => {
203
+ const sync = new AgentSpecSync({ Name: 'Test' });
204
+ expect(sync.spec.InvocationMode).toBe('Any');
205
+ });
206
+
207
+ it('should set default ModelSelectionMode to Agent Type', () => {
208
+ const sync = new AgentSpecSync({ Name: 'Test' });
209
+ expect(sync.spec.ModelSelectionMode).toBe('Agent Type');
210
+ });
211
+
212
+ it('should default Actions and SubAgents to empty arrays', () => {
213
+ const sync = new AgentSpecSync({ Name: 'Test' });
214
+ expect(sync.spec.Actions).toEqual([]);
215
+ expect(sync.spec.SubAgents).toEqual([]);
216
+ });
217
+
218
+ it('should default Steps and Paths to empty arrays', () => {
219
+ const sync = new AgentSpecSync({ Name: 'Test' });
220
+ expect(sync.spec.Steps).toEqual([]);
221
+ expect(sync.spec.Paths).toEqual([]);
222
+ });
223
+
224
+ it('should preserve provided values', () => {
225
+ const sync = new AgentSpecSync({
226
+ Name: 'Test',
227
+ MaxCostPerRun: 10.0,
228
+ MaxTokensPerRun: 5000,
229
+ InvocationMode: 'Chat',
230
+ });
231
+ expect(sync.spec.MaxCostPerRun).toBe(10.0);
232
+ expect(sync.spec.MaxTokensPerRun).toBe(5000);
233
+ expect(sync.spec.InvocationMode).toBe('Chat');
234
+ });
235
+ });
236
+
237
+ describe('SaveToDatabase', () => {
238
+ it('should skip save when not dirty and loaded', async () => {
239
+ const sync = new AgentSpecSync();
240
+ // Mark as loaded but not dirty
241
+ sync.markLoaded();
242
+ sync.spec.ID = 'existing-id';
243
+ // isDirty is false by default for empty constructor
244
+ const result = await sync.SaveToDatabase();
245
+ expect(result.success).toBe(true);
246
+ expect(result.mutations).toEqual([]);
247
+ });
248
+
249
+ it('should attempt save when dirty and return result', async () => {
250
+ const sync = new AgentSpecSync({ Name: 'New Agent' }, mockUser);
251
+ const result = await sync.SaveToDatabase();
252
+ // SaveToDatabase returns a result object with agentId, success, and mutations
253
+ expect(result.agentId).toBeDefined();
254
+ expect(typeof result.success).toBe('boolean');
255
+ expect(Array.isArray(result.mutations)).toBe(true);
256
+ });
257
+ });
258
+
259
+ describe('spec with actions', () => {
260
+ it('should initialize actions from partial spec', () => {
261
+ const sync = new AgentSpecSync({
262
+ Name: 'Test Agent',
263
+ Actions: [
264
+ {
265
+ AgentActionID: 'aa-1',
266
+ ActionID: 'action-1',
267
+ Status: 'Active',
268
+ },
269
+ ],
270
+ });
271
+ expect(sync.spec.Actions).toHaveLength(1);
272
+ expect(sync.spec.Actions![0].ActionID).toBe('action-1');
273
+ });
274
+ });
275
+
276
+ describe('spec with sub-agents', () => {
277
+ it('should initialize sub-agents from partial spec', () => {
278
+ const sync = new AgentSpecSync({
279
+ Name: 'Parent Agent',
280
+ SubAgents: [
281
+ {
282
+ Type: 'child',
283
+ SubAgent: {
284
+ ID: 'child-1',
285
+ Name: 'Child Agent',
286
+ StartingPayloadValidationMode: 'Fail',
287
+ },
288
+ },
289
+ ],
290
+ });
291
+ expect(sync.spec.SubAgents).toHaveLength(1);
292
+ expect(sync.spec.SubAgents![0].Type).toBe('child');
293
+ });
294
+ });
295
+
296
+ describe('spec with prompts', () => {
297
+ it('should initialize prompts from partial spec', () => {
298
+ const sync = new AgentSpecSync({
299
+ Name: 'Test Agent',
300
+ Prompts: [
301
+ { PromptID: 'p-1', PromptText: 'Hello', PromptRole: 'System', PromptPosition: 'First' },
302
+ ],
303
+ });
304
+ expect(sync.spec.Prompts).toHaveLength(1);
305
+ });
306
+ });
307
+
308
+ describe('spec with steps and paths', () => {
309
+ it('should initialize steps from partial spec', () => {
310
+ const sync = new AgentSpecSync({
311
+ Name: 'Flow Agent',
312
+ Steps: [
313
+ { ID: 'step-1', Name: 'Start', StepType: 'Action', StartingStep: true },
314
+ ],
315
+ Paths: [
316
+ { ID: 'path-1', OriginStepID: 'step-1', DestinationStepID: 'step-2', Priority: 1 },
317
+ ],
318
+ });
319
+ expect(sync.spec.Steps).toHaveLength(1);
320
+ expect(sync.spec.Paths).toHaveLength(1);
321
+ });
322
+ });
323
+ });
package/tsconfig.json CHANGED
@@ -4,6 +4,14 @@
4
4
  "outDir": "dist",
5
5
  "rootDir": "src"
6
6
  },
7
- "include": ["src/**/*"],
8
- "exclude": ["node_modules"]
9
- }
7
+ "include": [
8
+ "src/**/*"
9
+ ],
10
+ "exclude": [
11
+ "node_modules",
12
+ "src/__tests__/**",
13
+ "src/**/*.test.ts",
14
+ "src/**/*.spec.ts",
15
+ "vitest.config.ts"
16
+ ]
17
+ }
@@ -0,0 +1,11 @@
1
+ import { defineProject, mergeConfig } from 'vitest/config';
2
+ import sharedConfig from '../../../../vitest.shared';
3
+
4
+ export default mergeConfig(
5
+ sharedConfig,
6
+ defineProject({
7
+ test: {
8
+ environment: 'node',
9
+ },
10
+ })
11
+ );