@memberjunction/ai-agents 3.4.0 → 4.0.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 (97) hide show
  1. package/dist/AgentDataPreloader.d.ts +117 -1
  2. package/dist/AgentDataPreloader.d.ts.map +1 -1
  3. package/dist/AgentDataPreloader.js +156 -58
  4. package/dist/AgentDataPreloader.js.map +1 -1
  5. package/dist/AgentRunner.d.ts +212 -0
  6. package/dist/AgentRunner.d.ts.map +1 -1
  7. package/dist/AgentRunner.js +354 -103
  8. package/dist/AgentRunner.js.map +1 -1
  9. package/dist/PayloadChangeAnalyzer.d.ts +68 -0
  10. package/dist/PayloadChangeAnalyzer.d.ts.map +1 -1
  11. package/dist/PayloadChangeAnalyzer.js +68 -32
  12. package/dist/PayloadChangeAnalyzer.js.map +1 -1
  13. package/dist/PayloadFeedbackManager.d.ts +57 -1
  14. package/dist/PayloadFeedbackManager.d.ts.map +1 -1
  15. package/dist/PayloadFeedbackManager.js +66 -21
  16. package/dist/PayloadFeedbackManager.js.map +1 -1
  17. package/dist/PayloadManager.d.ts +286 -1
  18. package/dist/PayloadManager.d.ts.map +1 -1
  19. package/dist/PayloadManager.js +423 -50
  20. package/dist/PayloadManager.js.map +1 -1
  21. package/dist/__tests__/action-changes.test.d.ts +13 -0
  22. package/dist/__tests__/action-changes.test.d.ts.map +1 -1
  23. package/dist/__tests__/action-changes.test.js +57 -4
  24. package/dist/__tests__/action-changes.test.js.map +1 -1
  25. package/dist/__tests__/agent-memory-features.test.d.ts +52 -0
  26. package/dist/__tests__/agent-memory-features.test.d.ts.map +1 -1
  27. package/dist/__tests__/agent-memory-features.test.js +96 -13
  28. package/dist/__tests__/agent-memory-features.test.js.map +1 -1
  29. package/dist/__tests__/agent-type-prompt-params.test.d.ts +12 -0
  30. package/dist/__tests__/agent-type-prompt-params.test.d.ts.map +1 -1
  31. package/dist/__tests__/agent-type-prompt-params.test.js +112 -20
  32. package/dist/__tests__/agent-type-prompt-params.test.js.map +1 -1
  33. package/dist/__tests__/chat-handling-option.test.d.ts +26 -0
  34. package/dist/__tests__/chat-handling-option.test.d.ts.map +1 -1
  35. package/dist/__tests__/chat-handling-option.test.js +41 -2
  36. package/dist/__tests__/chat-handling-option.test.js.map +1 -1
  37. package/dist/agent-context-injector.d.ts +114 -0
  38. package/dist/agent-context-injector.d.ts.map +1 -1
  39. package/dist/agent-context-injector.js +138 -19
  40. package/dist/agent-context-injector.js.map +1 -1
  41. package/dist/agent-types/base-agent-type.d.ts +354 -0
  42. package/dist/agent-types/base-agent-type.d.ts.map +1 -1
  43. package/dist/agent-types/base-agent-type.js +288 -17
  44. package/dist/agent-types/base-agent-type.js.map +1 -1
  45. package/dist/agent-types/flow-agent-type.d.ts +328 -2
  46. package/dist/agent-types/flow-agent-type.d.ts.map +1 -1
  47. package/dist/agent-types/flow-agent-type.js +503 -63
  48. package/dist/agent-types/flow-agent-type.js.map +1 -1
  49. package/dist/agent-types/index.d.ts +14 -3
  50. package/dist/agent-types/index.d.ts.map +1 -1
  51. package/dist/agent-types/index.js +14 -23
  52. package/dist/agent-types/index.js.map +1 -1
  53. package/dist/agent-types/loop-agent-prompt-params.d.ts +190 -0
  54. package/dist/agent-types/loop-agent-prompt-params.d.ts.map +1 -1
  55. package/dist/agent-types/loop-agent-prompt-params.js +23 -6
  56. package/dist/agent-types/loop-agent-prompt-params.js.map +1 -1
  57. package/dist/agent-types/loop-agent-response-type.d.ts +62 -0
  58. package/dist/agent-types/loop-agent-response-type.d.ts.map +1 -1
  59. package/dist/agent-types/loop-agent-response-type.js +3 -2
  60. package/dist/agent-types/loop-agent-response-type.js.map +1 -1
  61. package/dist/agent-types/loop-agent-type.d.ts +139 -2
  62. package/dist/agent-types/loop-agent-type.d.ts.map +1 -1
  63. package/dist/agent-types/loop-agent-type.js +207 -35
  64. package/dist/agent-types/loop-agent-type.js.map +1 -1
  65. package/dist/base-agent.d.ts +1344 -1
  66. package/dist/base-agent.d.ts.map +1 -1
  67. package/dist/base-agent.js +2282 -211
  68. package/dist/base-agent.js.map +1 -1
  69. package/dist/index.d.ts +24 -14
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.js +25 -35
  72. package/dist/index.js.map +1 -1
  73. package/dist/memory-cleanup-agent.d.ts +53 -1
  74. package/dist/memory-cleanup-agent.d.ts.map +1 -1
  75. package/dist/memory-cleanup-agent.js +89 -21
  76. package/dist/memory-cleanup-agent.js.map +1 -1
  77. package/dist/memory-manager-agent.d.ts +61 -1
  78. package/dist/memory-manager-agent.d.ts.map +1 -1
  79. package/dist/memory-manager-agent.js +260 -116
  80. package/dist/memory-manager-agent.js.map +1 -1
  81. package/dist/services/AgentEmbeddingService.d.ts +16 -0
  82. package/dist/services/AgentEmbeddingService.d.ts.map +1 -0
  83. package/dist/services/AgentEmbeddingService.js +158 -0
  84. package/dist/services/AgentEmbeddingService.js.map +1 -0
  85. package/dist/types/AgentMatchResult.d.ts +18 -0
  86. package/dist/types/AgentMatchResult.d.ts.map +1 -0
  87. package/dist/types/AgentMatchResult.js +3 -0
  88. package/dist/types/AgentMatchResult.js.map +1 -0
  89. package/dist/types/payload-operations.d.ts +51 -0
  90. package/dist/types/payload-operations.d.ts.map +1 -1
  91. package/dist/types/payload-operations.js +54 -15
  92. package/dist/types/payload-operations.js.map +1 -1
  93. package/dist/utils/ConversationMessageResolver.d.ts +79 -1
  94. package/dist/utils/ConversationMessageResolver.d.ts.map +1 -1
  95. package/dist/utils/ConversationMessageResolver.js +99 -9
  96. package/dist/utils/ConversationMessageResolver.js.map +1 -1
  97. package/package.json +20 -19
@@ -1,41 +1,104 @@
1
- "use strict";
1
+ /**
2
+ * @fileoverview Implementation of the Flow Agent Type for deterministic workflow execution.
3
+ *
4
+ * The FlowAgentType enables agents to execute predefined workflows using a directed graph
5
+ * of steps and conditional paths. Unlike LoopAgentType which makes decisions based on
6
+ * LLM output, FlowAgentType follows a deterministic execution path based on boolean
7
+ * expressions evaluated against the current payload and step results.
8
+ *
9
+ * @module @memberjunction/ai-agents
10
+ * @author MemberJunction.com
11
+ * @since 2.76.0
12
+ */
2
13
  var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
14
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
15
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
16
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
17
  return c > 3 && r && Object.defineProperty(target, key, r), r;
7
18
  };
8
- Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.LoadFlowAgentType = exports.FlowAgentType = exports.FlowExecutionState = void 0;
10
- const global_1 = require("@memberjunction/global");
11
- const base_agent_type_1 = require("./base-agent-type");
12
- const core_1 = require("@memberjunction/core");
13
- const aiengine_1 = require("@memberjunction/aiengine");
14
- const actions_1 = require("@memberjunction/actions");
15
- const PayloadManager_1 = require("../PayloadManager");
16
- const ConversationMessageResolver_1 = require("../utils/ConversationMessageResolver");
17
- class FlowExecutionState {
19
+ import { RegisterClass, SafeExpressionEvaluator } from '@memberjunction/global';
20
+ import { BaseAgentType } from './base-agent-type.js';
21
+ import { LogError, LogStatus, LogStatusEx, IsVerboseLoggingEnabled } from '@memberjunction/core';
22
+ import { AIEngine } from '@memberjunction/aiengine';
23
+ import { ActionEngineServer } from '@memberjunction/actions';
24
+ import { PayloadManager } from '../PayloadManager.js';
25
+ import { ConversationMessageResolver } from '../utils/ConversationMessageResolver.js';
26
+ /**
27
+ * Flow execution state that tracks the progress through the workflow.
28
+ * This is maintained by the Flow Agent Type during execution.
29
+ *
30
+ * Note: Loop iteration tracking is now handled by BaseAgent._iterationContext.
31
+ * FlowExecutionState only tracks flow-specific navigation state.
32
+ */
33
+ export class FlowExecutionState {
18
34
  constructor(agentId) {
35
+ /** Set of completed step IDs */
19
36
  this.completedStepIds = new Set();
37
+ /** Map of step results by step ID */
20
38
  this.stepResults = new Map();
39
+ /** Ordered list of step IDs in execution order */
21
40
  this.executionPath = [];
22
41
  this.agentId = agentId;
23
42
  }
24
43
  }
25
- exports.FlowExecutionState = FlowExecutionState;
26
- let FlowAgentType = class FlowAgentType extends base_agent_type_1.BaseAgentType {
44
+ /**
45
+ * Implementation of the Flow Agent Type pattern.
46
+ *
47
+ * This agent type enables deterministic workflow execution where agents follow
48
+ * predefined paths through a graph of steps. Key features:
49
+ * - Graph-based workflow definition
50
+ * - Conditional path evaluation using safe boolean expressions
51
+ * - Support for Actions, Sub-Agents, and Prompts as step types
52
+ * - Deterministic execution with optional AI-driven decision points
53
+ *
54
+ * @class FlowAgentType
55
+ * @extends BaseAgentType
56
+ *
57
+ * @example
58
+ * ```typescript
59
+ * // Flow agents execute steps based on graph structure
60
+ * const flowAgent = new FlowAgentType();
61
+ * const nextStep = await flowAgent.DetermineNextStep(promptResult, payload);
62
+ *
63
+ * // Steps are determined by evaluating path conditions
64
+ * // Path: "payload.status == 'approved' && payload.amount < 1000"
65
+ * // Leads to: "Auto-Approve" step
66
+ * ```
67
+ */
68
+ let FlowAgentType = class FlowAgentType extends BaseAgentType {
27
69
  constructor() {
28
70
  super(...arguments);
29
- this._evaluator = new global_1.SafeExpressionEvaluator();
30
- this._payloadManager = new PayloadManager_1.PayloadManager();
71
+ this._evaluator = new SafeExpressionEvaluator();
72
+ this._payloadManager = new PayloadManager();
31
73
  }
74
+ /**
75
+ * Handles the initialization of the flow agent state that is specialized, additional state information
76
+ * specific to the Flow Agent Type
77
+ * @param params
78
+ * @returns
79
+ */
32
80
  async InitializeAgentTypeState(params) {
33
81
  const flowState = new FlowExecutionState(params.agent.ID);
34
82
  return flowState;
35
83
  }
84
+ /**
85
+ * Determines the next step based on the flow graph structure.
86
+ *
87
+ * For Flow agents, the next step is determined by:
88
+ * 1. Finding the current position in the graph
89
+ * 2. Evaluating conditions on outgoing paths
90
+ * 3. Following the highest priority valid path
91
+ * 4. Using prompt results only when current step type is 'Prompt'
92
+ *
93
+ * @param {AIPromptRunResult | null} promptResult - Result from prompt execution (null for non-prompt steps)
94
+ * @param {ExecuteAgentParams} params - The full execution parameters
95
+ *
96
+ * @returns {Promise<BaseAgentNextStep<P>>} The next step to execute
97
+ */
36
98
  async DetermineNextStep(promptResult, params, payload, agentTypeState) {
37
99
  try {
38
100
  const flowState = agentTypeState;
101
+ // If no current step, this should have been handled by DetermineInitialStep
39
102
  if (!flowState.currentStepId) {
40
103
  const startingSteps = await this.getStartingSteps(params.agent.ID);
41
104
  if (startingSteps.length === 0) {
@@ -43,26 +106,41 @@ let FlowAgentType = class FlowAgentType extends base_agent_type_1.BaseAgentType
43
106
  errorMessage: 'No starting steps defined for flow agent'
44
107
  });
45
108
  }
109
+ // For now, execute the first starting step
110
+ // Future enhancement: support parallel starting steps
46
111
  return await this.createStepForFlowNode(params, startingSteps[0], payload, flowState);
47
112
  }
113
+ // Get current step to check if it was a Prompt step
48
114
  const currentStep = await this.getStepById(flowState.currentStepId);
115
+ // If current step was a Prompt, update payload with the prompt result
49
116
  if (currentStep?.StepType === 'Prompt' && promptResult) {
117
+ // Parse the prompt result as JSON
50
118
  const promptResponse = this.parseJSONResponse(promptResult);
51
119
  if (promptResponse) {
120
+ // Check if the prompt response contains a Chat step request
121
+ // This handles the case where a Prompt step wants to return a message to the user
52
122
  if (promptResponse.nextStep?.type === 'Chat' ||
53
123
  (promptResponse.taskComplete && promptResponse.message)) {
124
+ // Return a Chat step to bubble the message back to the user
54
125
  return this.createNextStep('Chat', {
55
126
  message: promptResponse.message || 'Response from flow prompt step',
56
127
  reasoning: promptResponse.reasoning,
57
128
  confidence: promptResponse.confidence,
58
- terminate: true,
129
+ terminate: true, // Always terminate for chat responses
59
130
  newPayload: payload,
60
131
  previousPayload: payload
61
132
  });
62
133
  }
134
+ // Deep merge the prompt response into the current payload
135
+ // This preserves existing nested properties while adding/updating from the prompt
136
+ // Example: If payload has {decision: {Y: 4, Z: 2}} and prompt returns {decision: {x: "string"}},
137
+ // the result will be {decision: {x: "string", Y: 4, Z: 2}} instead of losing Y and Z
63
138
  const mergedPayload = this._payloadManager.deepMerge(payload, promptResponse);
139
+ // Copy merged result back to payload reference (modifying in place for consistency)
64
140
  Object.assign(payload, mergedPayload);
65
141
  }
142
+ // Store the prompt step result so path conditions can access it
143
+ // Create a result object with Success: true (prompt executed successfully)
66
144
  const promptStepResult = {
67
145
  Success: true,
68
146
  step: 'Success',
@@ -70,30 +148,39 @@ let FlowAgentType = class FlowAgentType extends base_agent_type_1.BaseAgentType
70
148
  rawResult: promptResult
71
149
  };
72
150
  flowState.stepResults.set(flowState.currentStepId, promptStepResult);
151
+ // Add to execution path if not already present
73
152
  if (!flowState.executionPath.includes(flowState.currentStepId)) {
74
153
  flowState.executionPath.push(flowState.currentStepId);
75
154
  }
155
+ // Mark step as completed
76
156
  flowState.completedStepIds.add(flowState.currentStepId);
77
157
  }
158
+ // Find valid paths from current step
159
+ // Pass params to enable data/context access in path conditions (Phase 1)
78
160
  const paths = await this.getValidPaths(flowState.currentStepId, payload, flowState, params);
79
161
  if (paths.length === 0) {
162
+ // No valid paths - flow is complete
80
163
  return this.createSuccessStep({
81
164
  message: 'Flow completed - no more paths to follow'
82
165
  });
83
166
  }
167
+ // Get the destination step for the highest priority path
84
168
  const nextStep = await this.getStepById(paths[0].DestinationStepID);
85
169
  if (!nextStep) {
86
170
  return this.createNextStep('Failed', {
87
171
  errorMessage: `Destination step not found: ${paths[0].DestinationStepID}`
88
172
  });
89
173
  }
174
+ // Check if the step is active
90
175
  if (nextStep.Status !== 'Active') {
176
+ // Step is disabled or pending - find next valid path
91
177
  for (let i = 1; i < paths.length; i++) {
92
178
  const alternateStep = await this.getStepById(paths[i].DestinationStepID);
93
179
  if (alternateStep && alternateStep.Status === 'Active') {
94
180
  return await this.createStepForFlowNode(params, alternateStep, payload, flowState);
95
181
  }
96
182
  }
183
+ // No active steps found in any path
97
184
  return this.createNextStep('Failed', {
98
185
  errorMessage: `No active steps found. Step '${nextStep.Name}' has status: ${nextStep.Status}`
99
186
  });
@@ -101,12 +188,19 @@ let FlowAgentType = class FlowAgentType extends base_agent_type_1.BaseAgentType
101
188
  return await this.createStepForFlowNode(params, nextStep, payload, flowState);
102
189
  }
103
190
  catch (error) {
104
- (0, core_1.LogError)(`Error in FlowAgentType.DetermineNextStep: ${error.message}`);
191
+ LogError(`Error in FlowAgentType.DetermineNextStep: ${error.message}`);
105
192
  return this.createNextStep('Failed', {
106
193
  errorMessage: `Flow execution error: ${error.message}`
107
194
  });
108
195
  }
109
196
  }
197
+ /**
198
+ * Injects flow-specific context into the prompt parameters.
199
+ *
200
+ * @param {P} payload - The payload to inject
201
+ * @param {AIPromptParams} prompt - The prompt parameters to update
202
+ * @param {object} agentInfo - Agent identification info
203
+ */
110
204
  async InjectPayload(payload, agentState, prompt, agentInfo) {
111
205
  if (!prompt) {
112
206
  throw new Error('Prompt parameters are required for payload injection');
@@ -114,7 +208,9 @@ let FlowAgentType = class FlowAgentType extends base_agent_type_1.BaseAgentType
114
208
  if (!prompt.data) {
115
209
  prompt.data = {};
116
210
  }
117
- prompt.data[base_agent_type_1.BaseAgentType.CURRENT_PAYLOAD_PLACEHOLDER] = payload || {};
211
+ // Inject standard payload
212
+ prompt.data[BaseAgentType.CURRENT_PAYLOAD_PLACEHOLDER] = payload || {};
213
+ // Add flow-specific context from our state tracking
118
214
  if (agentInfo.agentRunId) {
119
215
  const flowState = agentState;
120
216
  if (agentState) {
@@ -127,28 +223,66 @@ let FlowAgentType = class FlowAgentType extends base_agent_type_1.BaseAgentType
127
223
  }
128
224
  }
129
225
  }
226
+ /**
227
+ * Gets all starting steps for an agent
228
+ *
229
+ * @private
230
+ */
130
231
  async getStartingSteps(agentId) {
131
- const steps = aiengine_1.AIEngine.Instance.GetAgentSteps(agentId, 'Active');
232
+ const steps = AIEngine.Instance.GetAgentSteps(agentId, 'Active');
132
233
  return steps.filter(step => step.StartingStep).sort((a, b) => a.Name.localeCompare(b.Name));
133
234
  }
235
+ /**
236
+ * Gets a step by ID
237
+ *
238
+ * @private
239
+ */
134
240
  async getStepById(stepId) {
135
- return aiengine_1.AIEngine.Instance.GetAgentStepByID(stepId);
241
+ return AIEngine.Instance.GetAgentStepByID(stepId);
136
242
  }
243
+ /**
244
+ * Gets a step by name within an agent
245
+ *
246
+ * @private
247
+ */
137
248
  async getStepByName(agentId, stepName) {
138
- const steps = aiengine_1.AIEngine.Instance.GetAgentSteps(agentId);
249
+ const steps = AIEngine.Instance.GetAgentSteps(agentId);
139
250
  return steps.find(step => step.Name === stepName) || null;
140
251
  }
252
+ /**
253
+ * Gets all paths from a step and evaluates their conditions.
254
+ *
255
+ * The evaluation context includes:
256
+ * - payload: The current agent payload (persistent state)
257
+ * - stepResult: Result from the last executed step
258
+ * - flowContext: Flow execution metadata (current step, completed steps, path)
259
+ * - data: Transient template data from ExecuteAgentParams (NEW in Phase 1)
260
+ * - context: Runtime context from ExecuteAgentParams (NEW in Phase 1)
261
+ *
262
+ * This allows path conditions to access runtime parameters like:
263
+ * - `data.userApproval === true`
264
+ * - `context.environment === 'production'`
265
+ * - `data.retryCount < 3 && payload.status === 'pending'`
266
+ *
267
+ * @private
268
+ */
141
269
  async getValidPaths(stepId, payload, flowState, params) {
142
- const allPaths = aiengine_1.AIEngine.Instance.GetPathsFromStep(stepId)
270
+ // Load all paths from this step
271
+ const allPaths = AIEngine.Instance.GetPathsFromStep(stepId)
143
272
  .sort((a, b) => b.Priority - a.Priority);
144
273
  const validPaths = [];
274
+ // Evaluate each path's condition
145
275
  for (const path of allPaths) {
276
+ // If no condition, path is always valid
146
277
  if (!path.Condition) {
147
278
  validPaths.push(path);
148
279
  continue;
149
280
  }
150
281
  try {
282
+ // Create enhanced evaluation context
283
+ // Phase 1: Read-only access to data and context for path conditions
151
284
  const context = {
285
+ // Existing context properties
152
286
  payload,
153
287
  stepResult: this.getLastStepResult(flowState),
154
288
  flowContext: {
@@ -157,6 +291,9 @@ let FlowAgentType = class FlowAgentType extends base_agent_type_1.BaseAgentType
157
291
  executionPath: flowState.executionPath,
158
292
  stepCount: flowState.completedStepIds.size
159
293
  },
294
+ // NEW: Map params.data and params.context directly
295
+ // These enable deterministic routing based on runtime state
296
+ // without polluting the persistent payload
160
297
  data: params.data || {},
161
298
  context: params.context || {}
162
299
  };
@@ -165,41 +302,69 @@ let FlowAgentType = class FlowAgentType extends base_agent_type_1.BaseAgentType
165
302
  validPaths.push(path);
166
303
  }
167
304
  else if (!evalResult.success) {
168
- (0, core_1.LogError)(`Path condition failed: ${path.Condition}\n${evalResult.error}`);
305
+ // Only log errors when evaluation failed, not when condition is simply false
306
+ LogError(`Path condition failed: ${path.Condition}\n${evalResult.error}`);
169
307
  }
170
308
  }
171
309
  catch (error) {
172
- (0, core_1.LogError)(`Failed to evaluate path condition: ${error.message}`);
310
+ LogError(`Failed to evaluate path condition: ${error.message}`);
173
311
  }
174
312
  }
313
+ // If no valid paths with conditions, include paths with priority <= 0 (defaults)
175
314
  if (validPaths.length === 0) {
176
315
  return allPaths.filter(p => p.Priority <= 0 && !p.Condition);
177
316
  }
317
+ // Sort by priority (already sorted by query, but re-sort valid subset)
178
318
  return validPaths.sort((a, b) => b.Priority - a.Priority);
179
319
  }
320
+ /**
321
+ * Gets the last step result from the flow context
322
+ *
323
+ * @private
324
+ */
180
325
  getLastStepResult(flowState) {
181
326
  if (flowState.executionPath.length === 0)
182
327
  return null;
183
328
  const lastStepId = flowState.executionPath[flowState.executionPath.length - 1];
184
329
  return flowState.stepResults.get(lastStepId) || null;
185
330
  }
331
+ /**
332
+ * Helper to set a value on a target object, supporting array append syntax.
333
+ * If key ends with '[]', the value is pushed to an array (auto-initialized if needed).
334
+ *
335
+ * @private
336
+ */
186
337
  setMappedValue(target, key, value) {
187
338
  const isArrayAppend = key.endsWith('[]');
188
339
  const actualKey = isArrayAppend ? key.slice(0, -2) : key;
189
340
  if (isArrayAppend) {
341
+ // Initialize array if it doesn't exist
190
342
  if (!(actualKey in target)) {
191
343
  target[actualKey] = [];
192
344
  }
345
+ // Validate it's actually an array
193
346
  if (!Array.isArray(target[actualKey])) {
194
347
  throw new Error(`Cannot append to '${actualKey}': target is not an array. ` +
195
348
  `Use '${actualKey}' without [] suffix for property update.`);
196
349
  }
350
+ // Append the value
197
351
  target[actualKey].push(value);
198
352
  }
199
353
  else {
354
+ // Standard property assignment
200
355
  target[actualKey] = value;
201
356
  }
202
357
  }
358
+ /**
359
+ * Applies action output mapping to update the payload and extract special fields.
360
+ *
361
+ * Special fields ($message, $reasoning, $confidence) are prefixed with $ and are not
362
+ * added to the payload. Instead, they are stored separately in FlowExecutionState
363
+ * for use in the final response.
364
+ *
365
+ * @private
366
+ * @returns Object with payloadChange and specialFields
367
+ */
203
368
  applyActionOutputMapping(actionResult, _payload, mappingConfig) {
204
369
  try {
205
370
  const mapping = JSON.parse(mappingConfig);
@@ -211,10 +376,12 @@ let FlowAgentType = class FlowAgentType extends base_agent_type_1.BaseAgentType
211
376
  value = actionResult;
212
377
  }
213
378
  else if (outputParam.includes('.')) {
379
+ // Support dot notation for nested property access (e.g., "AgentSpec.ID")
214
380
  const parts = outputParam.split('.');
215
381
  value = actionResult;
216
382
  for (const part of parts) {
217
383
  if (value && typeof value === 'object' && !Array.isArray(value)) {
384
+ // Case-insensitive lookup at each level
218
385
  const actualKey = Object.keys(value).find(key => key.toLowerCase() === part.toLowerCase());
219
386
  value = actualKey ? value[actualKey] : undefined;
220
387
  }
@@ -225,12 +392,15 @@ let FlowAgentType = class FlowAgentType extends base_agent_type_1.BaseAgentType
225
392
  }
226
393
  }
227
394
  else {
395
+ // Simple case-insensitive lookup for non-dotted output parameters
228
396
  const actualKey = Object.keys(actionResult).find(key => key.toLowerCase() === outputParam.toLowerCase());
229
397
  value = actualKey ? actionResult[actualKey] : undefined;
230
398
  }
231
399
  if (value !== undefined) {
400
+ // Check if this is a special field mapping (starts with $)
232
401
  if (payloadPath.startsWith('$')) {
233
402
  const fieldName = payloadPath.substring(1).toLowerCase();
403
+ // Store in special fields instead of payload
234
404
  if (fieldName === 'message' && typeof value === 'string') {
235
405
  specialFields.message = value;
236
406
  }
@@ -240,21 +410,26 @@ let FlowAgentType = class FlowAgentType extends base_agent_type_1.BaseAgentType
240
410
  else if (fieldName === 'confidence' && typeof value === 'number') {
241
411
  specialFields.confidence = value;
242
412
  }
413
+ // Ignore unknown special fields with warning
243
414
  else {
244
- (0, core_1.LogError)(`Unknown special field in ActionOutputMapping: ${payloadPath}. Valid special fields are: $message, $reasoning, $confidence`);
415
+ LogError(`Unknown special field in ActionOutputMapping: ${payloadPath}. Valid special fields are: $message, $reasoning, $confidence`);
245
416
  }
246
417
  }
247
418
  else {
419
+ // Regular payload mapping
420
+ // Parse the path and build nested object
248
421
  const pathParts = payloadPath.split('.');
249
422
  let current = updateObj;
250
423
  for (let i = 0; i < pathParts.length - 1; i++) {
251
424
  const part = pathParts[i];
425
+ // Remove [] suffix for intermediate path parts
252
426
  const cleanPart = part.endsWith('[]') ? part.slice(0, -2) : part;
253
427
  if (!(cleanPart in current)) {
254
428
  current[cleanPart] = {};
255
429
  }
256
430
  current = current[cleanPart];
257
431
  }
432
+ // Use helper to support array append on final path part
258
433
  this.setMappedValue(current, pathParts[pathParts.length - 1], value);
259
434
  }
260
435
  }
@@ -268,26 +443,41 @@ let FlowAgentType = class FlowAgentType extends base_agent_type_1.BaseAgentType
268
443
  };
269
444
  }
270
445
  catch (error) {
271
- (0, core_1.LogError)(`Failed to parse ActionOutputMapping: ${error.message}`);
446
+ LogError(`Failed to parse ActionOutputMapping: ${error.message}`);
272
447
  return { payloadChange: null };
273
448
  }
274
449
  }
450
+ /**
451
+ * Creates a BaseAgentNextStep for a flow node.
452
+ *
453
+ * If the node is in agentTypeParams.skipSteps, the step is marked as skipped
454
+ * and we immediately evaluate paths to find the next step.
455
+ *
456
+ * @private
457
+ */
275
458
  async createStepForFlowNode(params, node, payload, flowState) {
459
+ // Check if this step should be skipped
276
460
  const flowParams = params.agentTypeParams;
277
461
  if (flowParams?.skipSteps && flowParams.skipSteps.some(s => s.ID === node.ID)) {
278
- (0, core_1.LogStatus)(`Flow Agent: Skipping step '${node.Name}' (via agentTypeParams.skipSteps)`);
462
+ LogStatus(`Flow Agent: Skipping step '${node.Name}' (via agentTypeParams.skipSteps)`);
463
+ // Update flow state to mark this as current step (for path evaluation)
279
464
  flowState.currentStepId = node.ID;
465
+ // Mark the step as completed (skipped)
280
466
  flowState.completedStepIds.add(node.ID);
281
467
  flowState.executionPath.push(node.ID);
468
+ // Store a skip marker as the step result
282
469
  flowState.stepResults.set(node.ID, { skipped: true, stepName: node.Name });
470
+ // Find the next step by evaluating paths from this skipped step
283
471
  const paths = await this.getValidPaths(node.ID, payload, flowState, params);
284
472
  if (paths.length === 0) {
473
+ // No more paths - flow is complete
285
474
  return this.createSuccessStep({
286
475
  message: `Flow completed after skipping step '${node.Name}' - no more paths to follow`,
287
476
  newPayload: payload,
288
477
  previousPayload: payload
289
478
  });
290
479
  }
480
+ // Get the destination step for the highest priority path
291
481
  const nextStep = await this.getStepById(paths[0].DestinationStepID);
292
482
  if (!nextStep) {
293
483
  return this.createNextStep('Failed', {
@@ -296,8 +486,10 @@ let FlowAgentType = class FlowAgentType extends base_agent_type_1.BaseAgentType
296
486
  previousPayload: payload
297
487
  });
298
488
  }
489
+ // Recursively create the step for the next node (which may also be skipped)
299
490
  return await this.createStepForFlowNode(params, nextStep, payload, flowState);
300
491
  }
492
+ // Update flow state to mark this as current step
301
493
  flowState.currentStepId = node.ID;
302
494
  switch (node.StepType) {
303
495
  case 'Action':
@@ -308,6 +500,7 @@ let FlowAgentType = class FlowAgentType extends base_agent_type_1.BaseAgentType
308
500
  previousPayload: payload
309
501
  });
310
502
  }
503
+ // Need to get the action name
311
504
  const actionName = await this.getActionName(node.ActionID);
312
505
  if (!actionName) {
313
506
  return this.createNextStep('Failed', {
@@ -316,15 +509,18 @@ let FlowAgentType = class FlowAgentType extends base_agent_type_1.BaseAgentType
316
509
  previousPayload: payload
317
510
  });
318
511
  }
512
+ // Check if we have output mapping configured
319
513
  const baseStep = this.createNextStep('Actions', {
320
514
  actions: [{
321
515
  name: actionName,
322
- params: {}
516
+ params: {} // Future: support parameter mapping
323
517
  }],
324
518
  terminate: false,
325
519
  newPayload: payload,
326
520
  previousPayload: payload
327
521
  });
522
+ // Store the mapping config in a special property for later use
523
+ // Always set stepId for action steps so PreProcessActionStep can access step entity
328
524
  baseStep.stepId = node.ID;
329
525
  if (node.ActionOutputMapping) {
330
526
  baseStep.actionOutputMapping = node.ActionOutputMapping;
@@ -338,6 +534,7 @@ let FlowAgentType = class FlowAgentType extends base_agent_type_1.BaseAgentType
338
534
  previousPayload: payload
339
535
  });
340
536
  }
537
+ // Need to get the sub-agent name
341
538
  const subAgentName = await this.getAgentName(node.SubAgentID);
342
539
  if (!subAgentName) {
343
540
  return this.createNextStep('Failed', {
@@ -346,18 +543,25 @@ let FlowAgentType = class FlowAgentType extends base_agent_type_1.BaseAgentType
346
543
  previousPayload: payload
347
544
  });
348
545
  }
546
+ // Use node description to define the sub-agent's task
547
+ // The full conversation history is preserved in params.conversationMessages
548
+ // and will be available to the sub-agent through BaseAgent.ExecuteSubAgent()
549
+ // Propagate context to sub-agent so it has access to runtime configuration,
550
+ // API keys, environment settings, etc. from the parent agent
349
551
  return this.createNextStep('Sub-Agent', {
350
552
  subAgent: {
351
553
  name: subAgentName,
352
554
  message: node.Description || '',
353
555
  terminateAfter: false,
354
- context: params.context
556
+ context: params.context // Propagate runtime context to sub-agent
355
557
  },
356
558
  terminate: false,
357
559
  newPayload: payload,
358
560
  previousPayload: payload
359
561
  });
360
562
  case 'Prompt':
563
+ // For prompt steps, we use Retry with a special marker
564
+ // The base agent will need to handle this case
361
565
  if (!node.PromptID) {
362
566
  return this.createNextStep('Failed', {
363
567
  errorMessage: `Prompt step '${node.Name}' has no PromptID`,
@@ -369,6 +573,7 @@ let FlowAgentType = class FlowAgentType extends base_agent_type_1.BaseAgentType
369
573
  step: 'Retry',
370
574
  message: node.Description || 'Executing prompt step for flow decision',
371
575
  terminate: false,
576
+ // Special marker for flow prompt steps
372
577
  flowPromptStepId: node.PromptID,
373
578
  newPayload: payload,
374
579
  previousPayload: payload
@@ -386,79 +591,137 @@ let FlowAgentType = class FlowAgentType extends base_agent_type_1.BaseAgentType
386
591
  });
387
592
  }
388
593
  }
594
+ /**
595
+ * Gets action name by ID
596
+ *
597
+ * @private
598
+ */
389
599
  async getActionName(actionId) {
390
- const action = actions_1.ActionEngineServer.Instance.Actions.find(a => a.ID === actionId);
600
+ const action = ActionEngineServer.Instance.Actions.find(a => a.ID === actionId);
391
601
  return action?.Name || null;
392
602
  }
603
+ /**
604
+ * Gets agent name by ID
605
+ *
606
+ * @private
607
+ */
393
608
  async getAgentName(agentId) {
394
- const agent = aiengine_1.AIEngine.Instance.Agents.find(a => a.ID === agentId);
609
+ const agent = AIEngine.Instance.Agents.find(a => a.ID === agentId);
395
610
  return agent?.Name || null;
396
611
  }
612
+ /**
613
+ * Processes action results and applies output mapping if configured
614
+ * This should be called by BaseAgent after action execution
615
+ *
616
+ * @public
617
+ */
397
618
  processActionResult(actionResult, _stepId, outputMapping, currentPayload) {
398
619
  if (!outputMapping) {
399
620
  return null;
400
621
  }
401
622
  const result = this.applyActionOutputMapping(actionResult, currentPayload || {}, outputMapping);
623
+ // Note: Special fields are ignored in this legacy method
624
+ // Use PostProcessActionStep for full special field support
402
625
  return result.payloadChange;
403
626
  }
627
+ /**
628
+ * Override of BaseAgentType's PreProcessActionStep to handle Flow-specific input mapping.
629
+ *
630
+ * This method checks if the current step has action input mapping configured
631
+ * and applies it to map payload values or static values to action input parameters.
632
+ *
633
+ * @override
634
+ * @param {AgentAction[]} actions - The actions that will be executed (modified in place)
635
+ * @param {P} currentPayload - The current payload
636
+ * @param {BaseAgentNextStep<P>} currentStep - The current step being executed
637
+ *
638
+ * @returns {Promise<void>} Actions are modified in place
639
+ *
640
+ * @since 2.76.0
641
+ */
404
642
  async PreProcessActionStep(actions, currentPayload, agentTypeState, currentStep, params) {
643
+ // Try to find the flow state and use its payload if available
405
644
  const stepMetadata = currentStep;
406
645
  const stepId = stepMetadata.stepId;
407
646
  if (!stepId || actions.length === 0) {
647
+ // No step ID or no actions to process
408
648
  return;
409
649
  }
410
- const stepEntity = aiengine_1.AIEngine.Instance.GetAgentStepByID(stepId);
650
+ // Get the AIAgentStep from cached metadata
651
+ const stepEntity = AIEngine.Instance.GetAgentStepByID(stepId);
411
652
  if (!stepEntity) {
412
- (0, core_1.LogError)(`Failed to find AIAgentStep for input mapping: ${stepId}`);
653
+ LogError(`Failed to find AIAgentStep for input mapping: ${stepId}`);
413
654
  return;
414
655
  }
415
656
  if (!stepEntity.ActionInputMapping) {
657
+ // No input mapping configured
416
658
  return;
417
659
  }
660
+ // For flow agents, we currently only support single action steps with input mapping
661
+ // Future enhancement: support mapping to multiple actions
418
662
  if (actions.length > 1) {
419
- (0, core_1.LogError)('Flow agent action input mapping currently only supports single action steps');
663
+ LogError('Flow agent action input mapping currently only supports single action steps');
420
664
  return;
421
665
  }
422
666
  try {
667
+ // Parse the input mapping configuration
668
+ // Expected format: { "paramName": "payload.path.to.value" | "static:value" | 123 | true }
423
669
  let inputMapping;
424
670
  if (typeof stepEntity.ActionInputMapping === 'string') {
671
+ // ActionInputMapping is a JSON string - parse it
425
672
  inputMapping = JSON.parse(stepEntity.ActionInputMapping);
426
673
  }
427
674
  else if (typeof stepEntity.ActionInputMapping === 'object' && stepEntity.ActionInputMapping !== null) {
675
+ // ActionInputMapping is already an object - use it directly
428
676
  inputMapping = stepEntity.ActionInputMapping;
429
677
  }
430
678
  else {
431
- (0, core_1.LogError)(`Invalid ActionInputMapping format for step ${stepEntity.Name}: ${stepEntity.ActionInputMapping}`);
679
+ // ActionInputMapping is null or invalid
680
+ LogError(`Invalid ActionInputMapping format for step ${stepEntity.Name}: ${stepEntity.ActionInputMapping}`);
432
681
  return;
433
682
  }
434
683
  const action = actions[0];
684
+ // Initialize params if not present
435
685
  if (!action.params) {
436
686
  action.params = {};
437
687
  }
688
+ // Apply each mapping
438
689
  for (const [paramName, mappingValue] of Object.entries(inputMapping)) {
690
+ // Use recursive resolution to handle nested objects, arrays, and primitive values
439
691
  const resolvedValue = this.resolveNestedValue(mappingValue, currentPayload, params);
692
+ // Set the parameter value
440
693
  action.params[paramName] = resolvedValue;
441
694
  }
442
- if ((0, core_1.IsVerboseLoggingEnabled)()) {
695
+ if (IsVerboseLoggingEnabled()) {
443
696
  console.log(`Applied action input mapping for step ${stepEntity.Name}:`, action.params);
444
697
  }
445
698
  }
446
699
  catch (error) {
447
- (0, core_1.LogError)(`Failed to apply action input mapping: ${error.message}`);
700
+ LogError(`Failed to apply action input mapping: ${error.message}`);
448
701
  }
449
702
  }
703
+ /**
704
+ * Helper method to get a value from a nested object path
705
+ * Supports both dot notation (obj.prop) and array indexing (arr[0])
706
+ *
707
+ * @private
708
+ */
450
709
  getValueFromPath(obj, path) {
451
710
  const parts = path.split('.');
452
711
  let current = obj;
453
712
  for (const part of parts) {
454
713
  if (!part)
455
714
  continue;
715
+ // Check if this part contains array indexing like "arrayName[0]"
456
716
  const arrayMatch = part.match(/^([^[]+)\[(\d+)\]$/);
457
717
  if (arrayMatch) {
718
+ // Extract array name and index
458
719
  const arrayName = arrayMatch[1];
459
720
  const index = parseInt(arrayMatch[2], 10);
721
+ // Navigate to the array
460
722
  if (current && typeof current === 'object' && arrayName in current) {
461
723
  current = current[arrayName];
724
+ // Access the array element
462
725
  if (Array.isArray(current) && index >= 0 && index < current.length) {
463
726
  current = current[index];
464
727
  }
@@ -471,6 +734,7 @@ let FlowAgentType = class FlowAgentType extends base_agent_type_1.BaseAgentType
471
734
  }
472
735
  }
473
736
  else {
737
+ // Regular property access
474
738
  if (current && typeof current === 'object' && part in current) {
475
739
  current = current[part];
476
740
  }
@@ -481,11 +745,25 @@ let FlowAgentType = class FlowAgentType extends base_agent_type_1.BaseAgentType
481
745
  }
482
746
  return current;
483
747
  }
748
+ /**
749
+ * Recursively resolves payload, static, data, context, and conversation references in nested objects.
750
+ *
751
+ * Supported prefixes:
752
+ * - `payload.path.to.value` - reads from persistent payload
753
+ * - `static:value` - literal static value
754
+ * - `data.path.to.value` - reads from params.data (template data)
755
+ * - `context.path.to.value` - reads from params.context (runtime context, e.g., API keys, environment)
756
+ * - `conversation[N].content` - reads from conversation messages
757
+ *
758
+ * @private
759
+ */
484
760
  resolveNestedValue(value, currentPayload, params) {
485
761
  if (typeof value === 'string') {
762
+ // Handle string values with payload/static/data/context/conversation resolution (case-insensitive)
486
763
  const trimmedValue = value.trim();
487
- if (ConversationMessageResolver_1.ConversationMessageResolver.isConversationReference(trimmedValue) && params?.conversationMessages) {
488
- return ConversationMessageResolver_1.ConversationMessageResolver.resolve(trimmedValue, params.conversationMessages);
764
+ // Check for conversation message references
765
+ if (ConversationMessageResolver.isConversationReference(trimmedValue) && params?.conversationMessages) {
766
+ return ConversationMessageResolver.resolve(trimmedValue, params.conversationMessages);
489
767
  }
490
768
  else if (trimmedValue.toLowerCase().startsWith('static:')) {
491
769
  return value.substring(value.indexOf(':') + 1);
@@ -501,6 +779,8 @@ let FlowAgentType = class FlowAgentType extends base_agent_type_1.BaseAgentType
501
779
  return this.getValueFromPath(params.data, path);
502
780
  }
503
781
  else if (trimmedValue.toLowerCase().startsWith('context.') && params?.context) {
782
+ // NEW: Support context. prefix for accessing runtime context
783
+ // This allows action input mappings to reference API keys, environment settings, etc.
504
784
  const pathStart = value.indexOf('.') + 1;
505
785
  const path = value.substring(pathStart);
506
786
  return this.getValueFromPath(params.context, path);
@@ -510,9 +790,11 @@ let FlowAgentType = class FlowAgentType extends base_agent_type_1.BaseAgentType
510
790
  }
511
791
  }
512
792
  else if (Array.isArray(value)) {
793
+ // Handle arrays - recursively resolve each element
513
794
  return value.map(item => this.resolveNestedValue(item, currentPayload, params));
514
795
  }
515
796
  else if (value && typeof value === 'object') {
797
+ // Handle objects - recursively resolve each property
516
798
  const resolvedObj = {};
517
799
  for (const [key, val] of Object.entries(value)) {
518
800
  resolvedObj[key] = this.resolveNestedValue(val, currentPayload, params);
@@ -520,22 +802,43 @@ let FlowAgentType = class FlowAgentType extends base_agent_type_1.BaseAgentType
520
802
  return resolvedObj;
521
803
  }
522
804
  else {
805
+ // Handle primitives (numbers, booleans, null, undefined)
523
806
  return value;
524
807
  }
525
808
  }
809
+ /**
810
+ * Override of BaseAgentType's PostProcessActionStep to handle Flow-specific logic.
811
+ *
812
+ * This method checks if the current step has action output mapping configured
813
+ * and applies it to update the payload accordingly.
814
+ *
815
+ * @override
816
+ * @param {ActionResult[]} actionResults - The results from action execution
817
+ * @param {AgentAction[]} actions - The actions that were executed
818
+ * @param {P} currentPayload - The current payload
819
+ * @param {BaseAgentNextStep<P>} currentStep - The current step being executed
820
+ *
821
+ * @returns {Promise<AgentPayloadChangeRequest<P> | null>} Payload changes from output mapping
822
+ */
526
823
  async PostProcessActionStep(actionResults, actions, currentPayload, agentTypeState, currentStep) {
824
+ // Check if this step has action output mapping configured
527
825
  const stepMetadata = currentStep;
528
826
  const outputMapping = stepMetadata.actionOutputMapping;
529
827
  const stepId = stepMetadata.stepId;
530
828
  if (!outputMapping || !stepId || actionResults.length === 0) {
829
+ // No mapping configured or no results to process
531
830
  return null;
532
831
  }
832
+ // For flow agents, we currently only support single action steps with output mapping
833
+ // Future enhancement: support mapping from multiple actions
533
834
  if (actionResults.length > 1) {
534
- (0, core_1.LogError)('Flow agent action output mapping currently only supports single action steps');
835
+ LogError('Flow agent action output mapping currently only supports single action steps');
535
836
  return null;
536
837
  }
838
+ // Extract output parameters from the action result
537
839
  const actionResult = actionResults[0];
538
840
  const outputParams = {};
841
+ // Filter for output parameters (Type === 'Output' or 'Both')
539
842
  if (actionResult.Params) {
540
843
  for (const param of actionResult.Params) {
541
844
  if (param.Type === 'Output' || param.Type === 'Both') {
@@ -543,61 +846,114 @@ let FlowAgentType = class FlowAgentType extends base_agent_type_1.BaseAgentType
543
846
  }
544
847
  }
545
848
  }
849
+ // Apply the mapping using our existing method
546
850
  const result = this.applyActionOutputMapping(outputParams, currentPayload, outputMapping);
547
851
  const payloadChange = result.payloadChange;
852
+ // Store special fields in flow state for later use
548
853
  const flowState = agentTypeState;
549
854
  if (result.specialFields) {
550
855
  if (!flowState.specialFields) {
551
856
  flowState.specialFields = {};
552
857
  }
858
+ // Merge special fields (later values override earlier ones)
553
859
  Object.assign(flowState.specialFields, result.specialFields);
554
860
  }
861
+ // Update flow state with the modified payload
862
+ // This ensures the payload persists when DetermineNextStep is called again
555
863
  if (payloadChange && payloadChange.updateElements) {
556
864
  const existingPayload = currentPayload || {};
865
+ // Apply the payload change using PayloadManager's merge capabilities
557
866
  const mergeResult = this._payloadManager.applyAgentChangeRequest(existingPayload, payloadChange, {
558
867
  logChanges: false,
559
- verbose: (0, core_1.IsVerboseLoggingEnabled)()
868
+ verbose: IsVerboseLoggingEnabled()
560
869
  });
870
+ // Log any warnings if present
561
871
  if (mergeResult.warnings && mergeResult.warnings.length > 0) {
562
- (0, core_1.LogError)(`Warnings during payload merge in flow state: ${mergeResult.warnings.join(', ')}`);
872
+ LogError(`Warnings during payload merge in flow state: ${mergeResult.warnings.join(', ')}`);
563
873
  }
564
874
  }
565
875
  return payloadChange;
566
876
  }
877
+ /**
878
+ * Determines the initial step for flow agent types.
879
+ *
880
+ * Flow agents look up their configured starting step instead of executing a prompt.
881
+ * If agentTypeParams.startAtStep is provided, the flow will begin at that step
882
+ * instead of the configured entry point.
883
+ *
884
+ * @param {ExecuteAgentParams} params - The full execution parameters
885
+ * @returns {Promise<BaseAgentNextStep<P> | null>} The initial step to execute, or null if flow context not ready
886
+ *
887
+ * @override
888
+ * @since 2.76.0
889
+ */
567
890
  async DetermineInitialStep(params, payload, agentTypeState) {
568
891
  const flowState = agentTypeState;
569
892
  const payloadToUse = payload || {};
893
+ // Check for startAtStep in agentTypeParams (FlowAgentExecuteParams)
570
894
  const flowParams = params.agentTypeParams;
571
895
  if (flowParams?.startAtStep) {
572
- const agentSteps = aiengine_1.AIEngine.Instance.GetAgentSteps(flowState.agentId);
896
+ // Validate the step belongs to this agent
897
+ const agentSteps = AIEngine.Instance.GetAgentSteps(flowState.agentId);
573
898
  const validStep = agentSteps.find(s => s.ID === flowParams.startAtStep.ID);
574
899
  if (!validStep) {
575
900
  return this.createNextStep('Failed', {
576
901
  errorMessage: `startAtStep '${flowParams.startAtStep.Name}' (ID: ${flowParams.startAtStep.ID}) does not belong to agent '${params.agent.Name}'`
577
902
  });
578
903
  }
579
- (0, core_1.LogStatus)(`Flow Agent: Starting at step '${validStep.Name}' (override via agentTypeParams.startAtStep)`);
904
+ LogStatus(`Flow Agent: Starting at step '${validStep.Name}' (override via agentTypeParams.startAtStep)`);
580
905
  return await this.createStepForFlowNode(params, validStep, payloadToUse, flowState);
581
906
  }
907
+ // Default behavior: start at the configured entry point
582
908
  const startingSteps = await this.getStartingSteps(flowState.agentId);
583
909
  if (startingSteps.length === 0) {
584
910
  return this.createNextStep('Failed', {
585
911
  errorMessage: 'No starting steps defined for flow agent'
586
912
  });
587
913
  }
914
+ // Execute the first starting step
915
+ // Future enhancement: support parallel starting steps
588
916
  return await this.createStepForFlowNode(params, startingSteps[0], payloadToUse, flowState);
589
917
  }
918
+ /**
919
+ * Pre-processes steps for flow agent types.
920
+ *
921
+ * For Flow agents, 'Retry' after actions means evaluate paths from the current step
922
+ * and continue the flow based on the updated payload. The only exception is when
923
+ * executing a Prompt step within the flow, which should execute normally.
924
+ *
925
+ * Also, 'Success' steps after sub-agents need to be evaluated in the same way as Retry after actions
926
+ *
927
+ * @param {ExecuteAgentParams} params - The full execution parameters
928
+ * @param {BaseAgentNextStep} step - The retry step that was returned
929
+ * @param {P} payload - The current payload
930
+ * @param {ATS} agentTypeState - The current agent type state
931
+ * @returns {Promise<BaseAgentNextStep<P> | null>} The next flow step, or null for prompt execution
932
+ *
933
+ * @override
934
+ * @since 2.76.0
935
+ */
590
936
  async PreProcessNextStep(params, step, payload, agentTypeState) {
937
+ // we only want to do special processing for retry, success, or failed steps
938
+ // Failed steps need to be evaluated for conditional failure paths
591
939
  if (step.step !== 'Retry' && step.step !== 'Success' && step.step !== 'Failed') {
940
+ // Not a retry, success, or failed step - use default processing
592
941
  return null;
593
942
  }
943
+ // Check if this is a special flow prompt step marker
594
944
  const flowStep = step;
595
945
  if (flowStep.flowPromptStepId) {
946
+ // This is a prompt step in the flow, let it execute normally
596
947
  return null;
597
948
  }
949
+ // Get the updated payload (after action execution)
598
950
  const payloadFromStep = payload || step.newPayload || params.payload || {};
599
951
  const flowState = agentTypeState;
952
+ // CRITICAL FIX: Don't overwrite the flow state's payload if it already has accumulated data
953
+ // The retry step only contains the action's output mapping result, not the full payload
954
+ // If flow state already has a payload with more data, keep it
600
955
  let currentPayload = payloadFromStep;
956
+ // We should have a current step ID from the flow state
601
957
  if (!flowState.currentStepId) {
602
958
  return this.createNextStep('Failed', {
603
959
  errorMessage: 'No current step in flow state after action execution',
@@ -605,15 +961,23 @@ let FlowAgentType = class FlowAgentType extends base_agent_type_1.BaseAgentType
605
961
  previousPayload: currentPayload
606
962
  });
607
963
  }
964
+ // Store the step result so path conditions can access it via stepResult
965
+ // The step parameter contains the result from the just-completed step (Sub-Agent, Action, or Prompt)
608
966
  flowState.stepResults.set(flowState.currentStepId, step);
967
+ // Add to execution path if not already present
609
968
  if (!flowState.executionPath.includes(flowState.currentStepId)) {
610
969
  flowState.executionPath.push(flowState.currentStepId);
611
970
  }
971
+ // Mark step as completed
612
972
  flowState.completedStepIds.add(flowState.currentStepId);
973
+ // Find valid paths from current step using updated payload
974
+ // Pass params to enable data/context access in path conditions (Phase 1)
613
975
  const paths = await this.getValidPaths(flowState.currentStepId, currentPayload, flowState, params);
614
976
  if (paths.length === 0) {
977
+ // No valid paths found
978
+ // If the previous step failed, propagate the failure with its error message
615
979
  if (step.step === 'Failed') {
616
- (0, core_1.LogStatusEx)({
980
+ LogStatusEx({
617
981
  message: `⚠️ Flow Agent: Step failed with no recovery path. Error: ${step.errorMessage || 'No error message'}`,
618
982
  verboseOnly: false
619
983
  });
@@ -624,10 +988,12 @@ let FlowAgentType = class FlowAgentType extends base_agent_type_1.BaseAgentType
624
988
  terminate: true
625
989
  });
626
990
  }
627
- (0, core_1.LogStatusEx)({
991
+ // Otherwise, flow completed successfully
992
+ LogStatusEx({
628
993
  message: '✅ Flow Agent: Flow completed successfully - no more paths to follow',
629
994
  verboseOnly: true
630
995
  });
996
+ // Include special fields from action output mappings if available
631
997
  const successStep = this.createSuccessStep({
632
998
  message: flowState.specialFields?.message || 'Flow completed - no more paths to follow',
633
999
  reasoning: flowState.specialFields?.reasoning,
@@ -637,24 +1003,28 @@ let FlowAgentType = class FlowAgentType extends base_agent_type_1.BaseAgentType
637
1003
  });
638
1004
  return successStep;
639
1005
  }
1006
+ // Get the destination step for the highest priority path
640
1007
  const nextStep = await this.getStepById(paths[0].DestinationStepID);
641
1008
  if (!nextStep) {
642
1009
  return this.createNextStep('Failed', {
643
1010
  errorMessage: `Destination step not found: ${paths[0].DestinationStepID}`
644
1011
  });
645
1012
  }
1013
+ // Log when we're navigating to a recovery path after a failure
646
1014
  if (step.step === 'Failed') {
647
- (0, core_1.LogStatusEx)({
1015
+ LogStatusEx({
648
1016
  message: `🔄 Flow Agent: Failure detected, navigating to recovery path: '${nextStep.Name}'`,
649
1017
  verboseOnly: false
650
1018
  });
651
1019
  }
1020
+ // Check if the step is active
652
1021
  if (nextStep.Status !== 'Active') {
1022
+ // Try alternate paths
653
1023
  for (let i = 1; i < paths.length; i++) {
654
1024
  const alternateStep = await this.getStepById(paths[i].DestinationStepID);
655
1025
  if (alternateStep && alternateStep.Status === 'Active') {
656
1026
  if (step.step === 'Failed') {
657
- (0, core_1.LogStatusEx)({
1027
+ LogStatusEx({
658
1028
  message: `🔄 Flow Agent: Using alternate recovery path: '${alternateStep.Name}'`,
659
1029
  verboseOnly: false
660
1030
  });
@@ -668,23 +1038,44 @@ let FlowAgentType = class FlowAgentType extends base_agent_type_1.BaseAgentType
668
1038
  previousPayload: currentPayload
669
1039
  });
670
1040
  }
1041
+ // Create the next step based on the flow node
671
1042
  return await this.createStepForFlowNode(params, nextStep, currentPayload, flowState);
672
1043
  }
1044
+ /**
1045
+ * Gets the prompt to use for a specific step.
1046
+ * Flow agents may use different prompts for different steps in the flow.
1047
+ *
1048
+ * @param {ExecuteAgentParams} params - The execution parameters
1049
+ * @param {AgentConfiguration} config - The loaded agent configuration
1050
+ * @param {BaseAgentNextStep | null} previousDecision - The previous step decision that may contain flow prompt info
1051
+ * @returns {Promise<AIPromptEntityExtended | null>} Custom prompt for flow steps, or default config.childPrompt
1052
+ *
1053
+ * @override
1054
+ * @since 2.76.0
1055
+ */
673
1056
  async GetPromptForStep(params, config, payload, agentTypeState, previousDecision) {
1057
+ // Check if this is a flow prompt step with a specific prompt ID
674
1058
  const flowDecision = previousDecision;
675
1059
  if (flowDecision?.flowPromptStepId) {
676
1060
  const promptId = flowDecision.flowPromptStepId;
677
- const promptEntity = aiengine_1.AIEngine.Instance.Prompts.find(p => p.ID === promptId);
1061
+ // Get the specific prompt from AIEngine (avoids database hit)
1062
+ const promptEntity = AIEngine.Instance.Prompts.find(p => p.ID === promptId);
678
1063
  if (promptEntity) {
679
1064
  return promptEntity;
680
1065
  }
681
1066
  else {
682
- (0, core_1.LogError)(`Failed to find flow prompt with ID: ${promptId} in AIEngine`);
1067
+ LogError(`Failed to find flow prompt with ID: ${promptId} in AIEngine`);
1068
+ // Fall back to default
683
1069
  return config.childPrompt || null;
684
1070
  }
685
1071
  }
1072
+ // For non-flow-prompt steps, use the default prompt
686
1073
  return config.childPrompt || null;
687
1074
  }
1075
+ /**
1076
+ * Converts a Flow Agent ForEach step into universal ForEachOperation format
1077
+ * that BaseAgent can execute
1078
+ */
688
1079
  async convertForEachStepToOperation(node, payload, flowState, params) {
689
1080
  if (!node.Configuration) {
690
1081
  return this.createNextStep('Failed', {
@@ -693,7 +1084,9 @@ let FlowAgentType = class FlowAgentType extends base_agent_type_1.BaseAgentType
693
1084
  previousPayload: payload
694
1085
  });
695
1086
  }
1087
+ // Parse base configuration from AIAgentStep
696
1088
  const baseConfig = JSON.parse(node.Configuration);
1089
+ // Build universal ForEachOperation
697
1090
  const forEach = {
698
1091
  collectionPath: baseConfig.collectionPath,
699
1092
  itemVariable: baseConfig.itemVariable,
@@ -704,8 +1097,9 @@ let FlowAgentType = class FlowAgentType extends base_agent_type_1.BaseAgentType
704
1097
  executionMode: baseConfig.executionMode,
705
1098
  maxConcurrency: baseConfig.maxConcurrency
706
1099
  };
1100
+ // Add action or subAgent based on LoopBodyType (using cached engine data - no await needed)
707
1101
  if (node.LoopBodyType === 'Action') {
708
- const action = aiengine_1.AIEngine.Instance.Actions.find(a => a.ID === node.ActionID);
1102
+ const action = AIEngine.Instance.Actions.find(a => a.ID === node.ActionID);
709
1103
  if (!action) {
710
1104
  return this.createNextStep('Failed', {
711
1105
  errorMessage: `Action not found for loop body: ${node.ActionID}`,
@@ -720,7 +1114,7 @@ let FlowAgentType = class FlowAgentType extends base_agent_type_1.BaseAgentType
720
1114
  };
721
1115
  }
722
1116
  else if (node.LoopBodyType === 'Sub-Agent') {
723
- const subAgent = aiengine_1.AIEngine.Instance.Agents.find(a => a.ID === node.SubAgentID);
1117
+ const subAgent = AIEngine.Instance.Agents.find(a => a.ID === node.SubAgentID);
724
1118
  if (!subAgent) {
725
1119
  return this.createNextStep('Failed', {
726
1120
  errorMessage: `Sub-Agent not found for loop body: ${node.SubAgentID}`,
@@ -732,7 +1126,7 @@ let FlowAgentType = class FlowAgentType extends base_agent_type_1.BaseAgentType
732
1126
  name: subAgent.Name,
733
1127
  message: node.Description || `Execute sub-agent: ${subAgent.Name}`,
734
1128
  templateParameters: {},
735
- context: params.context
1129
+ context: params.context // Propagate runtime context to sub-agent in loop
736
1130
  };
737
1131
  }
738
1132
  else if (node.LoopBodyType === 'Prompt') {
@@ -742,16 +1136,22 @@ let FlowAgentType = class FlowAgentType extends base_agent_type_1.BaseAgentType
742
1136
  previousPayload: payload
743
1137
  });
744
1138
  }
1139
+ // Store stepId in flow state for later (when loop completes, we need to navigate paths)
745
1140
  flowState.currentStepId = node.ID;
1141
+ // Return ForEach decision for BaseAgent to execute
746
1142
  return {
747
1143
  step: 'ForEach',
748
1144
  forEach,
749
1145
  terminate: false,
750
1146
  newPayload: payload,
751
1147
  previousPayload: payload,
752
- agentTypeData: { stepId: node.ID }
1148
+ agentTypeData: { stepId: node.ID } // Flow needs to remember which step this is
753
1149
  };
754
1150
  }
1151
+ /**
1152
+ * Converts a Flow Agent While step into universal WhileOperation format
1153
+ * that BaseAgent can execute
1154
+ */
755
1155
  async convertWhileStepToOperation(node, payload, flowState, params) {
756
1156
  if (!node.Configuration) {
757
1157
  return this.createNextStep('Failed', {
@@ -760,7 +1160,9 @@ let FlowAgentType = class FlowAgentType extends base_agent_type_1.BaseAgentType
760
1160
  previousPayload: payload
761
1161
  });
762
1162
  }
1163
+ // Parse base configuration
763
1164
  const baseConfig = JSON.parse(node.Configuration);
1165
+ // Build universal WhileOperation
764
1166
  const whileOp = {
765
1167
  condition: baseConfig.condition,
766
1168
  itemVariable: baseConfig.itemVariable,
@@ -768,8 +1170,9 @@ let FlowAgentType = class FlowAgentType extends base_agent_type_1.BaseAgentType
768
1170
  continueOnError: baseConfig.continueOnError,
769
1171
  delayBetweenIterationsMs: baseConfig.delayBetweenIterationsMs
770
1172
  };
1173
+ // Add action or subAgent based on LoopBodyType
771
1174
  if (node.LoopBodyType === 'Action') {
772
- const action = aiengine_1.AIEngine.Instance.Actions.find(a => a.ID === node.ActionID);
1175
+ const action = AIEngine.Instance.Actions.find(a => a.ID === node.ActionID);
773
1176
  if (!action) {
774
1177
  return this.createNextStep('Failed', {
775
1178
  errorMessage: `Action not found for loop body: ${node.ActionID}`,
@@ -784,7 +1187,7 @@ let FlowAgentType = class FlowAgentType extends base_agent_type_1.BaseAgentType
784
1187
  };
785
1188
  }
786
1189
  else if (node.LoopBodyType === 'Sub-Agent') {
787
- const subAgent = aiengine_1.AIEngine.Instance.Agents.find(a => a.ID === node.SubAgentID);
1190
+ const subAgent = AIEngine.Instance.Agents.find(a => a.ID === node.SubAgentID);
788
1191
  if (!subAgent) {
789
1192
  return this.createNextStep('Failed', {
790
1193
  errorMessage: `Sub-Agent not found for loop body: ${node.SubAgentID}`,
@@ -796,7 +1199,7 @@ let FlowAgentType = class FlowAgentType extends base_agent_type_1.BaseAgentType
796
1199
  name: subAgent.Name,
797
1200
  message: node.Description || `Execute sub-agent: ${subAgent.Name}`,
798
1201
  templateParameters: {},
799
- context: params.context
1202
+ context: params.context // Propagate runtime context to sub-agent in loop
800
1203
  };
801
1204
  }
802
1205
  else if (node.LoopBodyType === 'Prompt') {
@@ -806,7 +1209,9 @@ let FlowAgentType = class FlowAgentType extends base_agent_type_1.BaseAgentType
806
1209
  previousPayload: payload
807
1210
  });
808
1211
  }
1212
+ // Store stepId for later path navigation
809
1213
  flowState.currentStepId = node.ID;
1214
+ // Return While decision for BaseAgent to execute
810
1215
  return {
811
1216
  step: 'While',
812
1217
  while: whileOp,
@@ -816,31 +1221,63 @@ let FlowAgentType = class FlowAgentType extends base_agent_type_1.BaseAgentType
816
1221
  agentTypeData: { stepId: node.ID }
817
1222
  };
818
1223
  }
1224
+ /**
1225
+ * Flow agents don't need loop results injected as messages - they navigate paths
1226
+ * @override
1227
+ */
819
1228
  get InjectLoopResultsAsMessage() {
820
1229
  return false;
821
1230
  }
1231
+ /**
1232
+ * Flow agents don't require agent-level prompts - they use step-level prompts exclusively
1233
+ * @override
1234
+ */
822
1235
  get RequiresAgentLevelPrompts() {
823
1236
  return false;
824
1237
  }
1238
+ /**
1239
+ * Provides Flow-specific guidance for prompt configuration
1240
+ * @override
1241
+ */
825
1242
  GetPromptConfigurationGuidance() {
826
1243
  return ` - Flow agents use step-level prompts (StepType="Prompt" with PromptID)\n` +
827
1244
  ` - Prompts should be configured in agent steps, not AI Agent Prompts relationship\n` +
828
1245
  ` - Verify that prompts exist in AI Prompts table and are active`;
829
1246
  }
1247
+ /**
1248
+ * Flow agents should NEVER fall back to prompt execution for Success/Failed steps.
1249
+ * Flow agents are deterministic and driven by their graph structure (steps and paths).
1250
+ * When a step completes (Success or Failed), the agent should terminate rather than
1251
+ * retry with prompts.
1252
+ *
1253
+ * @override
1254
+ * @since 2.113.0
1255
+ */
830
1256
  async HandleStepFallback(step, config, params, payload, agentTypeState) {
1257
+ // Flow agents NEVER fall back to prompt execution
1258
+ // They are driven entirely by their graph structure (steps and paths)
1259
+ // Success/Failed steps should terminate, not retry prompts
831
1260
  if (step.step === 'Success' || step.step === 'Failed') {
1261
+ // Use spread to preserve all properties and just override terminate
832
1262
  return {
833
1263
  ...step,
834
1264
  terminate: true
835
1265
  };
836
1266
  }
1267
+ // For other step types, use default behavior (should not reach here)
837
1268
  return null;
838
1269
  }
1270
+ /**
1271
+ * Flow agents apply ActionOutputMapping after each iteration
1272
+ * @override
1273
+ */
839
1274
  AfterLoopIteration(iterationResult) {
840
1275
  const loopContext = iterationResult.loopContext;
1276
+ // Only apply for actions with output mapping
841
1277
  if (!iterationResult.actionResults || !loopContext.actionOutputMapping) {
842
1278
  return null;
843
1279
  }
1280
+ // Extract output parameters
844
1281
  const outputParams = {};
845
1282
  if (iterationResult.actionResults[0]?.Params) {
846
1283
  for (const param of iterationResult.actionResults[0].Params) {
@@ -849,22 +1286,25 @@ let FlowAgentType = class FlowAgentType extends base_agent_type_1.BaseAgentType
849
1286
  }
850
1287
  }
851
1288
  }
1289
+ // Replace iteration variables in output mapping paths before applying
852
1290
  let resolvedOutputMapping = loopContext.actionOutputMapping;
853
1291
  const indexVar = loopContext.indexVariable || 'index';
854
1292
  const itemVar = loopContext.itemVariable || 'item';
1293
+ // Replace [index] with [0], [1], etc.
855
1294
  resolvedOutputMapping = resolvedOutputMapping.replace(new RegExp(`\\[${indexVar}\\]`, 'g'), `[${iterationResult.index}]`);
1295
+ // Apply output mapping with resolved paths
856
1296
  const result = this.applyActionOutputMapping(outputParams, iterationResult.currentPayload, resolvedOutputMapping);
1297
+ // Note: Special fields are not supported in loop iterations
1298
+ // They only apply to final flow steps
857
1299
  if (result.payloadChange?.updateElements) {
1300
+ // Deep merge to preserve existing payload structure
858
1301
  return this._payloadManager.deepMerge(iterationResult.currentPayload, result.payloadChange.updateElements);
859
1302
  }
860
1303
  return null;
861
1304
  }
862
1305
  };
863
- exports.FlowAgentType = FlowAgentType;
864
- exports.FlowAgentType = FlowAgentType = __decorate([
865
- (0, global_1.RegisterClass)(base_agent_type_1.BaseAgentType, "FlowAgentType")
1306
+ FlowAgentType = __decorate([
1307
+ RegisterClass(BaseAgentType, "FlowAgentType")
866
1308
  ], FlowAgentType);
867
- function LoadFlowAgentType() {
868
- }
869
- exports.LoadFlowAgentType = LoadFlowAgentType;
1309
+ export { FlowAgentType };
870
1310
  //# sourceMappingURL=flow-agent-type.js.map