@memberjunction/ai-agents 2.107.0 → 2.109.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.
@@ -34,6 +34,7 @@ const ai_engine_base_1 = require("@memberjunction/ai-engine-base");
34
34
  const ai_core_plus_1 = require("@memberjunction/ai-core-plus");
35
35
  const AgentRunner_1 = require("./AgentRunner");
36
36
  const PayloadManager_1 = require("./PayloadManager");
37
+ const AgentDataPreloader_1 = require("./AgentDataPreloader");
37
38
  const _ = __importStar(require("lodash"));
38
39
  class BaseAgent {
39
40
  constructor() {
@@ -49,6 +50,7 @@ class BaseAgent {
49
50
  this._subAgentRuns = [];
50
51
  this._payloadManager = new PayloadManager_1.PayloadManager();
51
52
  this._validationRetryCount = 0;
53
+ this._contextRecoveryAttempted = false;
52
54
  }
53
55
  get AgentTypeState() {
54
56
  return this._agentTypeState;
@@ -132,6 +134,53 @@ class BaseAgent {
132
134
  this._agentTypeInstance = await base_agent_type_1.BaseAgentType.GetAgentTypeInstance(config.agentType);
133
135
  this._agentTypeState = await this._agentTypeInstance.InitializeAgentTypeState(params);
134
136
  }
137
+ async preloadAgentData(params) {
138
+ if (params.disableDataPreloading === true) {
139
+ this.logStatus(`⏭️ Data preloading disabled for agent '${params.agent.Name}'`, true, params);
140
+ return;
141
+ }
142
+ try {
143
+ const preloadedResult = await AgentDataPreloader_1.AgentDataPreloader.Instance.PreloadAgentData(params.agent.ID, params.contextUser, this._agentRun?.ID);
144
+ const totalSources = Object.keys(preloadedResult.data).length +
145
+ Object.keys(preloadedResult.context).length +
146
+ Object.keys(preloadedResult.payload).length;
147
+ if (totalSources > 0) {
148
+ const destinations = [];
149
+ if (Object.keys(preloadedResult.data).length > 0) {
150
+ destinations.push(`data(${Object.keys(preloadedResult.data).length})`);
151
+ }
152
+ if (Object.keys(preloadedResult.context).length > 0) {
153
+ destinations.push(`context(${Object.keys(preloadedResult.context).length})`);
154
+ }
155
+ if (Object.keys(preloadedResult.payload).length > 0) {
156
+ destinations.push(`payload(${Object.keys(preloadedResult.payload).length})`);
157
+ }
158
+ this.logStatus(`📊 Preloaded ${totalSources} data source(s) for agent '${params.agent.Name}': ${destinations.join(', ')}`, true, params);
159
+ params.data = {
160
+ ...preloadedResult.data,
161
+ ...params.data
162
+ };
163
+ params.context = {
164
+ ...preloadedResult.context,
165
+ ...params.context
166
+ };
167
+ params.payload = {
168
+ ...preloadedResult.payload,
169
+ ...params.payload
170
+ };
171
+ }
172
+ else {
173
+ this.logStatus(`📭 No data sources configured for agent '${params.agent.Name}'`, true, params);
174
+ }
175
+ }
176
+ catch (error) {
177
+ this.logError(`Failed to preload data for agent '${params.agent.Name}': ${error.message}`, {
178
+ agent: params.agent,
179
+ category: 'DataPreloading',
180
+ severity: 'warning'
181
+ });
182
+ }
183
+ }
135
184
  async Execute(params) {
136
185
  try {
137
186
  this.logStatus(`🤖 Starting execution of agent '${params.agent.Name}'`, true, params);
@@ -158,6 +207,8 @@ class BaseAgent {
158
207
  await this.initializeAgentRun(wrappedParams);
159
208
  this._validationRetryCount = 0;
160
209
  this._generalValidationRetryCount = 0;
210
+ this._contextRecoveryAttempted = false;
211
+ this._messageLifecycleCallback = params.onMessageLifecycle;
161
212
  await this.initializeEngines(params.contextUser);
162
213
  if (params.cancellationToken?.aborted) {
163
214
  return await this.createCancelledResult('Cancelled during initialization', params.contextUser);
@@ -183,6 +234,7 @@ class BaseAgent {
183
234
  });
184
235
  return await this.createFailureResult(config.errorMessage || 'Failed to load agent configuration', params.contextUser);
185
236
  }
237
+ await this.preloadAgentData(wrappedParams);
186
238
  await this.initializeAgentType(wrappedParams, config);
187
239
  this.logStatus(`🚀 Executing agent '${params.agent.Name}' internal logic`, true, params);
188
240
  const executionResult = await this.executeAgentInternal(wrappedParams, config);
@@ -219,6 +271,7 @@ class BaseAgent {
219
271
  if (params.cancellationToken?.aborted) {
220
272
  throw new Error('Cancelled during execution');
221
273
  }
274
+ await this.pruneAndCompactExpiredMessages(params, stepCount);
222
275
  this.logStatus(`🔄 Executing step ${stepCount + 1} for agent '${params.agent.Name}'`, true, params);
223
276
  const nextStep = await this.executeNextStep(params, config, currentNextStep);
224
277
  stepCount++;
@@ -897,6 +950,163 @@ class BaseAgent {
897
950
  }
898
951
  return { exceeded: false };
899
952
  }
953
+ isFatalPromptError(promptResult) {
954
+ if (!promptResult?.chatResult?.errorInfo) {
955
+ return false;
956
+ }
957
+ const errorInfo = promptResult.chatResult.errorInfo;
958
+ if (errorInfo.severity === 'Fatal') {
959
+ return true;
960
+ }
961
+ const fatalErrorTypes = [
962
+ 'ContextLengthExceeded',
963
+ 'Authentication',
964
+ 'InvalidRequest'
965
+ ];
966
+ return fatalErrorTypes.includes(errorInfo.errorType);
967
+ }
968
+ contentToString(content) {
969
+ if (typeof content === 'string') {
970
+ return content;
971
+ }
972
+ return content.map((block) => {
973
+ if (block.type === 'text') {
974
+ return block.content;
975
+ }
976
+ return `[${block.type}: ${block.content}]`;
977
+ }).join('\n');
978
+ }
979
+ smartTrimContent(content, maxLength = 1000) {
980
+ const originalLength = content.length;
981
+ try {
982
+ const data = JSON.parse(content);
983
+ if (Array.isArray(data)) {
984
+ const itemsToKeep = 10;
985
+ if (data.length > itemsToKeep) {
986
+ const truncated = data.slice(0, itemsToKeep);
987
+ const trimmed = JSON.stringify(truncated, null, 2) +
988
+ `\n\n... (${(data.length - itemsToKeep).toLocaleString()} more items truncated due to context length)`;
989
+ return {
990
+ trimmed,
991
+ strategy: 'JSON array truncation',
992
+ originalLength
993
+ };
994
+ }
995
+ }
996
+ else if (typeof data === 'object' && data !== null) {
997
+ const truncated = {};
998
+ let fieldCount = 0;
999
+ const maxFields = 20;
1000
+ for (const [key, value] of Object.entries(data)) {
1001
+ if (fieldCount >= maxFields) {
1002
+ truncated['...'] = `(${Object.keys(data).length - maxFields} more fields truncated)`;
1003
+ break;
1004
+ }
1005
+ if (typeof value === 'string' && value.length > 200) {
1006
+ truncated[key] = value.substring(0, 200) + '... (truncated)';
1007
+ }
1008
+ else if (Array.isArray(value) && value.length > 5) {
1009
+ truncated[key] = [...value.slice(0, 5), `... (${value.length - 5} more items)`];
1010
+ }
1011
+ else {
1012
+ truncated[key] = value;
1013
+ }
1014
+ fieldCount++;
1015
+ }
1016
+ return {
1017
+ trimmed: JSON.stringify(truncated, null, 2),
1018
+ strategy: 'JSON object field truncation',
1019
+ originalLength
1020
+ };
1021
+ }
1022
+ }
1023
+ catch {
1024
+ }
1025
+ if (content.includes('\n')) {
1026
+ const lines = content.split('\n');
1027
+ const firstLine = lines[0];
1028
+ if (firstLine.includes(',') && lines.length > 1) {
1029
+ const rowsToKeep = 10;
1030
+ if (lines.length > rowsToKeep + 1) {
1031
+ const header = lines[0];
1032
+ const dataRows = lines.slice(1, rowsToKeep + 1);
1033
+ const trimmed = [header, ...dataRows].join('\n') +
1034
+ `\n... (${(lines.length - rowsToKeep - 1).toLocaleString()} more rows truncated due to context length)`;
1035
+ return {
1036
+ trimmed,
1037
+ strategy: 'CSV row truncation',
1038
+ originalLength
1039
+ };
1040
+ }
1041
+ }
1042
+ }
1043
+ if (content.length > maxLength) {
1044
+ return {
1045
+ trimmed: content.substring(0, maxLength) + '\n\n... (truncated due to context length)',
1046
+ strategy: 'character truncation',
1047
+ originalLength
1048
+ };
1049
+ }
1050
+ return {
1051
+ trimmed: content,
1052
+ strategy: 'no truncation needed',
1053
+ originalLength
1054
+ };
1055
+ }
1056
+ async attemptContextRecovery(params, payload, errorMessage) {
1057
+ this.logStatus(`⚠️ Context length exceeded - attempting recovery by trimming conversation messages`, true, params);
1058
+ const messages = params.conversationMessages;
1059
+ let lastUserMessageIndex = -1;
1060
+ for (let i = messages.length - 1; i >= 0; i--) {
1061
+ if (messages[i].role === 'user') {
1062
+ lastUserMessageIndex = i;
1063
+ break;
1064
+ }
1065
+ }
1066
+ if (lastUserMessageIndex === -1) {
1067
+ this.logStatus(`❌ Context recovery failed: No trimmable user messages found`, true, params);
1068
+ return {
1069
+ errorMessage: `Context overflow with no trimmable messages: ${errorMessage}`,
1070
+ step: 'Failed',
1071
+ terminate: true,
1072
+ previousPayload: payload,
1073
+ newPayload: payload
1074
+ };
1075
+ }
1076
+ const originalMessage = messages[lastUserMessageIndex];
1077
+ const contentString = this.contentToString(originalMessage.content);
1078
+ const trimResult = this.smartTrimContent(contentString, 1000);
1079
+ this.logStatus(`✂️ Trimmed message from ${trimResult.originalLength.toLocaleString()} to ${trimResult.trimmed.length.toLocaleString()} characters using ${trimResult.strategy}`, true, params);
1080
+ messages[lastUserMessageIndex] = {
1081
+ role: 'user',
1082
+ content: `⚠️ CONTEXT OVERFLOW RECOVERY ⚠️
1083
+
1084
+ The previous step returned a result that exceeded the context window (${(trimResult.originalLength - trimResult.trimmed.length).toLocaleString()} characters truncated).
1085
+
1086
+ Here is a PARTIAL result from the previous action:
1087
+ ---
1088
+ ${trimResult.trimmed}
1089
+ ---
1090
+
1091
+ ❗ THE ABOVE IS INCOMPLETE - the full result was too large for the context window.
1092
+
1093
+ RECOMMENDED ACTIONS:
1094
+ 1. Use a different action with more specific filters to get smaller result sets
1095
+ 2. Request data in batches or pages instead of all at once
1096
+ 3. Ask the user to clarify scope to narrow the query
1097
+ 4. If you need the full data, acknowledge the limitation and ask the user how to proceed
1098
+
1099
+ Please choose an alternative approach to complete your task.`
1100
+ };
1101
+ return {
1102
+ step: 'Retry',
1103
+ retryReason: 'Context length recovery - previous result too large',
1104
+ retryInstructions: 'The previous action result exceeded context limits. Use more specific filters or request data in smaller batches.',
1105
+ terminate: false,
1106
+ previousPayload: payload,
1107
+ newPayload: payload
1108
+ };
1109
+ }
900
1110
  async processNextStep(nextStep, params, agentType, promptResult, currentPayload, currentStep) {
901
1111
  const validatedNextStep = await this.validateNextStep(params, nextStep, currentPayload, this._agentRun, currentStep);
902
1112
  const guardrailCheckedStep = await this.checkExecutionGuardrails(params, validatedNextStep, currentPayload, this._agentRun, currentStep);
@@ -1006,16 +1216,18 @@ class BaseAgent {
1006
1216
  throw new Error(`Error executing actions: ${error.message}`);
1007
1217
  }
1008
1218
  }
1009
- async ExecuteSubAgent(params, subAgentRequest, subAgent, stepEntity, payload) {
1219
+ async ExecuteSubAgent(params, subAgentRequest, subAgent, stepEntity, payload, contextMessage) {
1010
1220
  try {
1011
1221
  this.logStatus(`🤖 Executing sub-agent '${subAgentRequest.name}'`, true, params);
1012
1222
  const runner = new AgentRunner_1.AgentRunner();
1013
- const subAgentMessages = [
1014
- {
1015
- role: 'user',
1016
- content: subAgentRequest.message
1017
- }
1018
- ];
1223
+ const subAgentMessages = [];
1224
+ if (contextMessage) {
1225
+ subAgentMessages.push(contextMessage);
1226
+ }
1227
+ subAgentMessages.push({
1228
+ role: 'user',
1229
+ content: subAgentRequest.message
1230
+ });
1019
1231
  this.logStatus(`📨 Sub-agent message: "${subAgentRequest.message}"`, true, params);
1020
1232
  if (subAgentRequest.templateParameters) {
1021
1233
  this.logStatus(`📎 Template parameters: ${JSON.stringify(subAgentRequest.templateParameters)}`, true, params);
@@ -1134,6 +1346,25 @@ class BaseAgent {
1134
1346
  ValueType: param.ValueType
1135
1347
  };
1136
1348
  }
1349
+ formatParamValueForMessage(value, maxLength = 100) {
1350
+ if (value === null || value === undefined) {
1351
+ return 'null';
1352
+ }
1353
+ let stringValue;
1354
+ if (typeof value === 'string') {
1355
+ stringValue = value;
1356
+ }
1357
+ else if (typeof value === 'object') {
1358
+ stringValue = JSON.stringify(value);
1359
+ }
1360
+ else {
1361
+ stringValue = String(value);
1362
+ }
1363
+ if (stringValue.length > maxLength) {
1364
+ return `\`${stringValue.substring(0, maxLength)}...\``;
1365
+ }
1366
+ return `\`${stringValue}\``;
1367
+ }
1137
1368
  getAgentTypeName(typeID) {
1138
1369
  const agentType = aiengine_1.AIEngine.Instance.AgentTypes.find(at => at.ID === typeID);
1139
1370
  return agentType?.Name || 'Unknown';
@@ -1364,6 +1595,10 @@ class BaseAgent {
1364
1595
  }
1365
1596
  switch (previousDecision.step) {
1366
1597
  case 'Retry':
1598
+ if (previousDecision.messageIndex !== undefined) {
1599
+ const currentStepCount = this._agentRun?.Steps?.length || 0;
1600
+ this.executeExpandMessageStep(previousDecision, params, currentStepCount);
1601
+ }
1367
1602
  return await this.executePromptStep(params, config, previousDecision);
1368
1603
  case 'Sub-Agent':
1369
1604
  return await this.processSubAgentStep(params, previousDecision);
@@ -1466,6 +1701,25 @@ class BaseAgent {
1466
1701
  stepEntity.TargetLogID = promptResult.promptRun.ID;
1467
1702
  stepEntity.PromptRun = promptResult.promptRun;
1468
1703
  }
1704
+ if (!promptResult.success) {
1705
+ await this.finalizeStepEntity(stepEntity, false, promptResult.errorMessage);
1706
+ const isFatal = this.isFatalPromptError(promptResult);
1707
+ const isContextOverflow = isFatal &&
1708
+ promptResult.chatResult?.errorInfo?.errorType === 'ContextLengthExceeded';
1709
+ if (isContextOverflow && !this._contextRecoveryAttempted) {
1710
+ this._contextRecoveryAttempted = true;
1711
+ this.logStatus(`⚠️ Context length exceeded - attempting recovery by trimming conversation`, true, params);
1712
+ return await this.attemptContextRecovery(params, payload, promptResult.errorMessage || 'Context length exceeded');
1713
+ }
1714
+ this.logStatus(`❌ Prompt execution failed: ${promptResult.errorMessage} (fatal: ${isFatal})`, true, params);
1715
+ return {
1716
+ errorMessage: promptResult.errorMessage || 'Prompt execution failed',
1717
+ step: 'Failed',
1718
+ terminate: isFatal,
1719
+ previousPayload: payload,
1720
+ newPayload: payload
1721
+ };
1722
+ }
1469
1723
  if (params.cancellationToken?.aborted) {
1470
1724
  await this.finalizeStepEntity(stepEntity, false, 'Cancelled during prompt execution');
1471
1725
  const cancelledResult = await this.createCancelledResult('Cancelled during prompt execution', params.contextUser);
@@ -1631,7 +1885,7 @@ class BaseAgent {
1631
1885
  }
1632
1886
  }
1633
1887
  stepEntity.PayloadAtStart = JSON.stringify(previousDecision.newPayload);
1634
- const subAgentResult = await this.ExecuteSubAgent(params, subAgentRequest, subAgentEntity, stepEntity, scopedPayload);
1888
+ const subAgentResult = await this.ExecuteSubAgent(params, subAgentRequest, subAgentEntity, stepEntity, scopedPayload, undefined);
1635
1889
  let mergedPayload = previousDecision.newPayload;
1636
1890
  let currentStepPayloadChangeResult = undefined;
1637
1891
  if (subAgentResult.success) {
@@ -1843,12 +2097,34 @@ class BaseAgent {
1843
2097
  this.incrementExecutionCount(subAgentEntity.ID);
1844
2098
  try {
1845
2099
  stepEntity.PayloadAtStart = JSON.stringify(previousDecision.newPayload);
1846
- const subAgentResult = await this.ExecuteSubAgent(params, subAgentRequest, subAgentEntity, stepEntity, undefined);
2100
+ let initialSubAgentPayload = undefined;
2101
+ if (relationship.SubAgentInputMapping) {
2102
+ const mapped = this.applySubAgentInputMapping(previousDecision.newPayload, relationship.SubAgentInputMapping);
2103
+ if (mapped && Object.keys(mapped).length > 0) {
2104
+ initialSubAgentPayload = mapped;
2105
+ if (params.verbose === true || (0, core_1.IsVerboseLoggingEnabled)()) {
2106
+ (0, core_1.LogStatus)(`Related sub-agent '${subAgentRequest.name}' receiving mapped payload: ${JSON.stringify(Object.keys(mapped))}`);
2107
+ }
2108
+ }
2109
+ }
2110
+ let contextPaths = [];
2111
+ if (relationship.SubAgentContextPaths) {
2112
+ try {
2113
+ contextPaths = JSON.parse(relationship.SubAgentContextPaths);
2114
+ }
2115
+ catch (parseError) {
2116
+ (0, core_1.LogError)(`Failed to parse SubAgentContextPaths for sub-agent ${subAgentRequest.name}: ${parseError.message}`);
2117
+ }
2118
+ }
2119
+ const contextMessage = this.prepareRelatedSubAgentContextMessage(previousDecision.newPayload, contextPaths);
2120
+ if (contextMessage && (params.verbose === true || (0, core_1.IsVerboseLoggingEnabled)())) {
2121
+ (0, core_1.LogStatus)(`Related sub-agent '${subAgentRequest.name}' receiving context from paths: ${contextPaths.join(', ')}`);
2122
+ }
2123
+ const subAgentResult = await this.ExecuteSubAgent(params, subAgentRequest, subAgentEntity, stepEntity, initialSubAgentPayload, contextMessage);
1847
2124
  let mergedPayload = previousDecision.newPayload;
1848
2125
  let currentStepPayloadChangeResult = undefined;
1849
- const relationshipWithMapping = relationship;
1850
- if (subAgentResult.success && relationshipWithMapping.SubAgentOutputMapping) {
1851
- const payloadChange = this.applySubAgentOutputMapping(subAgentResult.payload, previousDecision.newPayload, relationshipWithMapping.SubAgentOutputMapping);
2126
+ if (subAgentResult.success && relationship.SubAgentOutputMapping) {
2127
+ const payloadChange = this.applySubAgentOutputMapping(subAgentResult.payload, previousDecision.newPayload, relationship.SubAgentOutputMapping);
1852
2128
  if (payloadChange && payloadChange.updateElements) {
1853
2129
  const mergeResult = this._payloadManager.applyAgentChangeRequest(previousDecision.newPayload, payloadChange, {
1854
2130
  validateChanges: true,
@@ -1872,7 +2148,7 @@ class BaseAgent {
1872
2148
  finalStep: subAgentResult.agentRun?.FinalStep,
1873
2149
  errorMessage: subAgentResult.agentRun?.ErrorMessage,
1874
2150
  stepCount: subAgentResult.agentRun?.Steps?.length || 0,
1875
- hasMergedPayload: !!(relationshipWithMapping.SubAgentOutputMapping && mergedPayload !== previousDecision.newPayload)
2151
+ hasMergedPayload: !!(relationship.SubAgentOutputMapping && mergedPayload !== previousDecision.newPayload)
1876
2152
  },
1877
2153
  shouldTerminate: shouldTerminate,
1878
2154
  nextStep: shouldTerminate ? 'success' : 'retry',
@@ -1880,6 +2156,9 @@ class BaseAgent {
1880
2156
  payloadChangeResult: currentStepPayloadChangeResult
1881
2157
  })
1882
2158
  };
2159
+ if (stepEntity) {
2160
+ stepEntity.PayloadAtEnd = JSON.stringify(mergedPayload);
2161
+ }
1883
2162
  await this.finalizeStepEntity(stepEntity, subAgentResult.success, subAgentResult.agentRun?.ErrorMessage, outputData);
1884
2163
  if (subAgentResult.agentRun?.FinalStep === 'Chat') {
1885
2164
  return {
@@ -1901,9 +2180,6 @@ class BaseAgent {
1901
2180
  role: 'user',
1902
2181
  content: `Related sub-agent "${subAgentRequest.name}" completed:\n${JSON.stringify(subAgentSummary, null, 2)}`
1903
2182
  });
1904
- if (stepEntity) {
1905
- stepEntity.PayloadAtEnd = JSON.stringify(mergedPayload);
1906
- }
1907
2183
  if (this._agentRun) {
1908
2184
  this._agentRun.FinalPayloadObject = mergedPayload;
1909
2185
  }
@@ -1965,6 +2241,72 @@ class BaseAgent {
1965
2241
  return null;
1966
2242
  }
1967
2243
  }
2244
+ applySubAgentInputMapping(parentPayload, mappingConfig) {
2245
+ try {
2246
+ const mapping = JSON.parse(mappingConfig);
2247
+ const subAgentPayload = {};
2248
+ for (const [parentPath, subAgentPath] of Object.entries(mapping)) {
2249
+ let value;
2250
+ if (parentPath === '*') {
2251
+ value = parentPayload;
2252
+ }
2253
+ else {
2254
+ value = this.getValueFromPath(parentPayload, parentPath);
2255
+ }
2256
+ if (value !== undefined) {
2257
+ const pathParts = subAgentPath.split('.');
2258
+ let current = subAgentPayload;
2259
+ for (let i = 0; i < pathParts.length - 1; i++) {
2260
+ const part = pathParts[i];
2261
+ if (!(part in current)) {
2262
+ current[part] = {};
2263
+ }
2264
+ current = current[part];
2265
+ }
2266
+ current[pathParts[pathParts.length - 1]] = value;
2267
+ }
2268
+ }
2269
+ if (Object.keys(subAgentPayload).length === 0) {
2270
+ return null;
2271
+ }
2272
+ return subAgentPayload;
2273
+ }
2274
+ catch (error) {
2275
+ (0, core_1.LogError)(`Failed to parse SubAgentInputMapping: ${error.message}`);
2276
+ return null;
2277
+ }
2278
+ }
2279
+ prepareRelatedSubAgentContextMessage(parentPayload, contextPaths) {
2280
+ if (!contextPaths || contextPaths.length === 0) {
2281
+ return null;
2282
+ }
2283
+ if (contextPaths.includes('*')) {
2284
+ return {
2285
+ role: 'user',
2286
+ content: `Parent Agent Context:\n\n${JSON.stringify(parentPayload, null, 2)}`
2287
+ };
2288
+ }
2289
+ const contextData = {};
2290
+ for (const path of contextPaths) {
2291
+ const value = this.getValueFromPath(parentPayload, path);
2292
+ if (value !== undefined) {
2293
+ contextData[path] = value;
2294
+ }
2295
+ }
2296
+ if (Object.keys(contextData).length === 0) {
2297
+ return null;
2298
+ }
2299
+ const contextLines = Object.entries(contextData).map(([key, value]) => {
2300
+ const valueStr = typeof value === 'object'
2301
+ ? JSON.stringify(value, null, 2)
2302
+ : String(value);
2303
+ return `${key}:\n${valueStr}`;
2304
+ });
2305
+ return {
2306
+ role: 'user',
2307
+ content: `Parent Agent Context:\n\n${contextLines.join('\n\n')}`
2308
+ };
2309
+ }
1968
2310
  getValueFromPath(obj, path) {
1969
2311
  const parts = path.split('.');
1970
2312
  let current = obj;
@@ -2009,35 +2351,34 @@ class BaseAgent {
2009
2351
  let progressMessage;
2010
2352
  if (actions.length === 1) {
2011
2353
  const aa = actions[0];
2012
- progressMessage = `Executing action: **${aa.name}**`;
2354
+ progressMessage = `Executing **${aa.name}** action`;
2013
2355
  if (aa.params && Object.keys(aa.params).length > 0) {
2014
2356
  const paramsList = Object.entries(aa.params)
2015
2357
  .map(([key, value]) => {
2016
- const displayValue = typeof value === 'object'
2017
- ? JSON.stringify(value, null, 2)
2018
- : String(value);
2019
- return ` - \`${key}\`: ${displayValue}`;
2358
+ const displayValue = this.formatParamValueForMessage(value);
2359
+ return `• **${key}**: ${displayValue}`;
2020
2360
  })
2021
2361
  .join('\n');
2022
- progressMessage += `\n${paramsList}`;
2362
+ progressMessage += ` with parameters:\n${paramsList}`;
2363
+ }
2364
+ else {
2365
+ progressMessage += '.';
2023
2366
  }
2024
2367
  }
2025
2368
  else {
2026
- progressMessage = `Executing ${actions.length} actions:`;
2027
- actions.forEach(aa => {
2028
- progressMessage += `\n\n• **${aa.name}**`;
2369
+ progressMessage = `Executing **${actions.length} actions** in parallel:\n\n` + actions.map((aa, index) => {
2370
+ let actionText = `${index + 1}. **${aa.name}**`;
2029
2371
  if (aa.params && Object.keys(aa.params).length > 0) {
2030
2372
  const paramsList = Object.entries(aa.params)
2031
2373
  .map(([key, value]) => {
2032
- const displayValue = typeof value === 'object'
2033
- ? JSON.stringify(value, null, 2)
2034
- : String(value);
2035
- return ` - \`${key}\`: ${displayValue}`;
2374
+ const displayValue = this.formatParamValueForMessage(value);
2375
+ return ` • **${key}**: ${displayValue}`;
2036
2376
  })
2037
2377
  .join('\n');
2038
- progressMessage += `\n${paramsList}`;
2378
+ actionText += `\n${paramsList}`;
2039
2379
  }
2040
- });
2380
+ return actionText;
2381
+ }).join('\n\n');
2041
2382
  }
2042
2383
  params.onProgress?.({
2043
2384
  step: 'action_execution',
@@ -2049,9 +2390,38 @@ class BaseAgent {
2049
2390
  },
2050
2391
  displayMode: 'live'
2051
2392
  });
2052
- const actionMessage = actions.length === 1
2053
- ? `I'm executing the "${actions[0].name}" action...`
2054
- : `I'm executing ${actions.length} actions to gather the information needed:\n${actions.map(a => `• ${a.name}`).join('\n')}`;
2393
+ let actionMessage;
2394
+ if (actions.length === 1) {
2395
+ const aa = actions[0];
2396
+ actionMessage = `I'm executing the **${aa.name}** action`;
2397
+ if (aa.params && Object.keys(aa.params).length > 0) {
2398
+ const paramsList = Object.entries(aa.params)
2399
+ .map(([key, value]) => {
2400
+ const displayValue = this.formatParamValueForMessage(value);
2401
+ return `• **${key}**: ${displayValue}`;
2402
+ })
2403
+ .join('\n');
2404
+ actionMessage += ` with parameters:\n${paramsList}`;
2405
+ }
2406
+ else {
2407
+ actionMessage += '.';
2408
+ }
2409
+ }
2410
+ else {
2411
+ actionMessage = `I'm executing **${actions.length} actions** in parallel:\n\n` + actions.map((aa, index) => {
2412
+ let actionText = `${index + 1}. **${aa.name}**`;
2413
+ if (aa.params && Object.keys(aa.params).length > 0) {
2414
+ const paramsList = Object.entries(aa.params)
2415
+ .map(([key, value]) => {
2416
+ const displayValue = this.formatParamValueForMessage(value);
2417
+ return ` • **${key}**: ${displayValue}`;
2418
+ })
2419
+ .join('\n');
2420
+ actionText += `\n${paramsList}`;
2421
+ }
2422
+ return actionText;
2423
+ }).join('\n\n');
2424
+ }
2055
2425
  params.conversationMessages.push({
2056
2426
  role: 'assistant',
2057
2427
  content: actionMessage
@@ -2120,9 +2490,44 @@ class BaseAgent {
2120
2490
  });
2121
2491
  const failedActions = actionSummaries.filter(a => !a.success);
2122
2492
  const resultsMessage = (failedActions.length > 0 ? `${failedActions.length} of ${actionSummaries.length} failed:` : `Action results:`) + `\n${JSON.stringify(actionSummaries, null, 2)}`;
2493
+ let metadata;
2494
+ const agentActionConfigs = actionResults
2495
+ .map(r => agentActions.find(aa => aa.ActionID === r.actionEntity.ID))
2496
+ .filter(aa => aa != null);
2497
+ if (agentActionConfigs.length > 0) {
2498
+ let minExpirationTurns = null;
2499
+ let expirationMode = 'None';
2500
+ let compactMode;
2501
+ let compactLength;
2502
+ let compactPromptId;
2503
+ for (const agentAction of agentActionConfigs) {
2504
+ if (agentAction.ResultExpirationTurns != null) {
2505
+ if (minExpirationTurns === null || agentAction.ResultExpirationTurns < minExpirationTurns) {
2506
+ minExpirationTurns = agentAction.ResultExpirationTurns;
2507
+ expirationMode = agentAction.ResultExpirationMode || 'None';
2508
+ compactMode = agentAction.CompactMode;
2509
+ compactLength = agentAction.CompactLength;
2510
+ compactPromptId = agentAction.CompactPromptID;
2511
+ }
2512
+ }
2513
+ }
2514
+ if (minExpirationTurns !== null && expirationMode !== 'None') {
2515
+ const currentStepCount = this._agentRun?.Steps?.length || 0;
2516
+ metadata = {
2517
+ turnAdded: currentStepCount,
2518
+ messageType: 'action-result',
2519
+ expirationTurns: minExpirationTurns,
2520
+ expirationMode: expirationMode,
2521
+ compactMode: compactMode,
2522
+ compactLength: compactLength,
2523
+ compactPromptId: compactPromptId
2524
+ };
2525
+ }
2526
+ }
2123
2527
  params.conversationMessages.push({
2124
2528
  role: 'user',
2125
- content: resultsMessage
2529
+ content: resultsMessage,
2530
+ metadata: metadata
2126
2531
  });
2127
2532
  let finalPayload = currentPayload;
2128
2533
  try {
@@ -2188,6 +2593,7 @@ class BaseAgent {
2188
2593
  confidence: previousDecision.confidence,
2189
2594
  previousPayload: previousDecision.previousPayload,
2190
2595
  newPayload: previousDecision.newPayload || previousDecision.previousPayload,
2596
+ suggestedResponses: previousDecision.suggestedResponses
2191
2597
  };
2192
2598
  }
2193
2599
  async createFailureResult(errorMessage, contextUser) {
@@ -2257,7 +2663,8 @@ class BaseAgent {
2257
2663
  return {
2258
2664
  success: finalStep.step === 'Success' || finalStep.step === 'Chat',
2259
2665
  payload,
2260
- agentRun: this._agentRun
2666
+ agentRun: this._agentRun,
2667
+ suggestedResponses: finalStep.suggestedResponses
2261
2668
  };
2262
2669
  }
2263
2670
  calculateTokenStats() {
@@ -2318,6 +2725,236 @@ class BaseAgent {
2318
2725
  }
2319
2726
  return violations;
2320
2727
  }
2728
+ async pruneAndCompactExpiredMessages(params, currentTurn) {
2729
+ const messagesToCompact = [];
2730
+ const messagesToRemove = [];
2731
+ for (let i = 0; i < params.conversationMessages.length; i++) {
2732
+ const msg = params.conversationMessages[i];
2733
+ if (!msg.metadata?.expirationTurns && msg.metadata?.expirationTurns !== 0) {
2734
+ continue;
2735
+ }
2736
+ if (msg.metadata.expirationMode === 'None') {
2737
+ continue;
2738
+ }
2739
+ const turnAdded = msg.metadata.turnAdded || 0;
2740
+ const turnsAlive = currentTurn - turnAdded;
2741
+ if (turnsAlive > msg.metadata.expirationTurns) {
2742
+ msg.metadata.isExpired = true;
2743
+ if (msg.metadata.expirationMode === 'Remove') {
2744
+ messagesToRemove.push(i);
2745
+ this.emitMessageLifecycleEvent({
2746
+ type: 'message-expired',
2747
+ turn: currentTurn,
2748
+ messageIndex: i,
2749
+ message: msg,
2750
+ reason: `Expired after ${turnsAlive} turns (limit: ${msg.metadata.expirationTurns})`
2751
+ });
2752
+ }
2753
+ else if (msg.metadata.expirationMode === 'Compact') {
2754
+ if (msg.metadata.compactMode) {
2755
+ messagesToCompact.push({
2756
+ index: i,
2757
+ message: msg,
2758
+ metadata: {
2759
+ compactMode: msg.metadata.compactMode,
2760
+ compactLength: msg.metadata.compactLength || 500,
2761
+ compactPromptId: msg.metadata.compactPromptId || '',
2762
+ originalLength: typeof msg.content === 'string'
2763
+ ? msg.content.length
2764
+ : JSON.stringify(msg.content).length
2765
+ }
2766
+ });
2767
+ }
2768
+ }
2769
+ }
2770
+ }
2771
+ const preserveOriginal = params.messageExpirationOverride?.preserveOriginalContent !== false;
2772
+ for (const item of messagesToCompact) {
2773
+ const originalContent = item.message.content;
2774
+ const compacted = await this.compactMessage(item.message, item.metadata, params);
2775
+ const originalTokens = this.estimateTokens(originalContent);
2776
+ const compactedTokens = this.estimateTokens(compacted);
2777
+ const tokensSaved = originalTokens - compactedTokens;
2778
+ params.conversationMessages[item.index] = {
2779
+ ...item.message,
2780
+ content: compacted,
2781
+ metadata: {
2782
+ ...item.message.metadata,
2783
+ wasCompacted: true,
2784
+ originalContent: preserveOriginal ? originalContent : undefined,
2785
+ originalLength: item.metadata.originalLength,
2786
+ tokensSaved,
2787
+ canExpand: preserveOriginal
2788
+ }
2789
+ };
2790
+ this.emitMessageLifecycleEvent({
2791
+ type: 'message-compacted',
2792
+ turn: currentTurn,
2793
+ messageIndex: item.index,
2794
+ message: params.conversationMessages[item.index],
2795
+ reason: `Compacted using ${item.metadata.compactMode} (saved ${tokensSaved} tokens)`,
2796
+ tokensSaved
2797
+ });
2798
+ }
2799
+ for (let i = messagesToRemove.length - 1; i >= 0; i--) {
2800
+ const index = messagesToRemove[i];
2801
+ const removed = params.conversationMessages.splice(index, 1)[0];
2802
+ this.emitMessageLifecycleEvent({
2803
+ type: 'message-removed',
2804
+ turn: currentTurn,
2805
+ messageIndex: index,
2806
+ message: removed,
2807
+ reason: 'Removed due to expiration'
2808
+ });
2809
+ }
2810
+ if (params.verbose && (messagesToCompact.length > 0 || messagesToRemove.length > 0)) {
2811
+ const totalSaved = messagesToCompact.reduce((sum, item) => {
2812
+ const msg = params.conversationMessages[item.index];
2813
+ return sum + (msg.metadata?.tokensSaved || 0);
2814
+ }, 0);
2815
+ console.log(`[Turn ${currentTurn}] Message pruning: ` +
2816
+ `${messagesToCompact.length} compacted (saved ~${totalSaved} tokens), ` +
2817
+ `${messagesToRemove.length} removed`);
2818
+ }
2819
+ }
2820
+ async createCompactionStep(prompt, message, params) {
2821
+ if (!this._agentRun) {
2822
+ throw new Error('Cannot create compaction step: agent run not initialized');
2823
+ }
2824
+ const md = new core_1.Metadata();
2825
+ const step = await md.GetEntityObject('MJ: AI Agent Run Steps', params.contextUser);
2826
+ step.NewRecord();
2827
+ step.AgentRunID = this._agentRun.ID;
2828
+ step.StepType = 'Prompt';
2829
+ step.Status = 'Running';
2830
+ step.InputData = JSON.stringify({
2831
+ stepName: 'Message Compaction',
2832
+ description: `Compacting message using AI Summary (${message.metadata?.messageType || 'unknown'} from turn ${message.metadata?.turnAdded || 0})`,
2833
+ messageType: message.metadata?.messageType,
2834
+ turnAdded: message.metadata?.turnAdded,
2835
+ originalLength: message.metadata?.originalLength ||
2836
+ (typeof message.content === 'string' ? message.content.length : JSON.stringify(message.content).length),
2837
+ compactMode: 'AI Summary',
2838
+ promptId: prompt.ID,
2839
+ promptName: prompt.Name
2840
+ });
2841
+ await step.Save();
2842
+ return step;
2843
+ }
2844
+ async updateCompactionStep(step, result, message, params) {
2845
+ step.Status = result.success ? 'Completed' : 'Failed';
2846
+ const promptTokens = result.promptTokens || 0;
2847
+ const completionTokens = result.completionTokens || 0;
2848
+ step.OutputData = JSON.stringify({
2849
+ success: result.success,
2850
+ summaryLength: result.result?.summary?.length || 0,
2851
+ tokensSaved: message.metadata?.tokensSaved || 0,
2852
+ promptTokens,
2853
+ completionTokens,
2854
+ totalTokens: promptTokens + completionTokens,
2855
+ cost: result.cost
2856
+ });
2857
+ if (!result.success) {
2858
+ step.ErrorMessage = result.errorMessage || 'AI Summary compaction failed';
2859
+ }
2860
+ await step.Save();
2861
+ }
2862
+ async compactMessage(message, metadata, params) {
2863
+ const originalContent = typeof message.content === 'string'
2864
+ ? message.content
2865
+ : JSON.stringify(message.content);
2866
+ switch (metadata.compactMode) {
2867
+ case 'First N Chars': {
2868
+ const length = metadata.compactLength;
2869
+ if (originalContent.length <= length) {
2870
+ return originalContent;
2871
+ }
2872
+ const truncated = originalContent.substring(0, length);
2873
+ return `${truncated}...\n\n[Compacted: showing first ${length} of ${originalContent.length} characters. Agent can request expansion if needed.]`;
2874
+ }
2875
+ case 'AI Summary': {
2876
+ try {
2877
+ const promptId = metadata.compactPromptId || this.getSystemDefaultCompactPromptId();
2878
+ const prompt = aiengine_1.AIEngine.Instance.Prompts.find(p => p.ID === promptId);
2879
+ if (!prompt) {
2880
+ console.warn(`Compact prompt ${promptId} not found, falling back to First N Chars`);
2881
+ return this.compactMessage(message, { ...metadata, compactMode: 'First N Chars' }, params);
2882
+ }
2883
+ const step = await this.createCompactionStep(prompt, message, params);
2884
+ const promptParams = new ai_core_plus_1.AIPromptParams();
2885
+ promptParams.prompt = prompt;
2886
+ promptParams.data = {
2887
+ originalContent,
2888
+ originalLength: metadata.originalLength,
2889
+ targetLength: metadata.compactLength || 500,
2890
+ messageType: message.metadata?.messageType || 'unknown',
2891
+ turnAdded: message.metadata?.turnAdded || 0
2892
+ };
2893
+ promptParams.contextUser = params.contextUser;
2894
+ const runner = new ai_prompts_1.AIPromptRunner();
2895
+ const result = await runner.ExecutePrompt(promptParams);
2896
+ await this.updateCompactionStep(step, result, message, params);
2897
+ if (!result.success || !result.result?.summary) {
2898
+ console.warn('AI summary failed, falling back to First N Chars');
2899
+ return this.compactMessage(message, { ...metadata, compactMode: 'First N Chars' }, params);
2900
+ }
2901
+ return `[AI Summary of ${metadata.originalLength} chars. Agent can request full expansion if needed.]\n\n${result.result.summary}`;
2902
+ }
2903
+ catch (error) {
2904
+ console.error('Error during AI summary:', error);
2905
+ return this.compactMessage(message, { ...metadata, compactMode: 'First N Chars' }, params);
2906
+ }
2907
+ }
2908
+ default:
2909
+ return originalContent;
2910
+ }
2911
+ }
2912
+ getSystemDefaultCompactPromptId() {
2913
+ const prompt = aiengine_1.AIEngine.Instance.Prompts.find(p => p.Name === 'Compact Agent Message');
2914
+ if (!prompt) {
2915
+ console.warn('System default compact prompt not found. Ensure "Compact Agent Message" prompt exists.');
2916
+ return '';
2917
+ }
2918
+ return prompt.ID;
2919
+ }
2920
+ estimateTokens(content) {
2921
+ const text = typeof content === 'string'
2922
+ ? content
2923
+ : JSON.stringify(content);
2924
+ return Math.ceil(text.length / 4);
2925
+ }
2926
+ emitMessageLifecycleEvent(event) {
2927
+ if (this._messageLifecycleCallback) {
2928
+ this._messageLifecycleCallback(event);
2929
+ }
2930
+ }
2931
+ executeExpandMessageStep(request, params, currentTurn) {
2932
+ const messageIndex = request.messageIndex;
2933
+ const reason = request.expandReason;
2934
+ if (messageIndex === undefined || messageIndex < 0 || messageIndex >= params.conversationMessages.length) {
2935
+ console.warn(`Cannot expand message: index ${messageIndex} out of bounds`);
2936
+ return;
2937
+ }
2938
+ const message = params.conversationMessages[messageIndex];
2939
+ if (!message.metadata?.canExpand || !message.metadata?.originalContent) {
2940
+ console.warn(`Cannot expand message at index ${messageIndex}: not expandable or no original content`);
2941
+ return;
2942
+ }
2943
+ message.content = message.metadata.originalContent;
2944
+ message.metadata.wasCompacted = false;
2945
+ message.metadata.canExpand = false;
2946
+ delete message.metadata.originalContent;
2947
+ this.emitMessageLifecycleEvent({
2948
+ type: 'message-expanded',
2949
+ turn: currentTurn,
2950
+ messageIndex,
2951
+ message,
2952
+ reason: reason || 'Agent requested expansion'
2953
+ });
2954
+ if (params.verbose) {
2955
+ console.log(`[Turn ${currentTurn}] Expanded message at index ${messageIndex}`);
2956
+ }
2957
+ }
2321
2958
  }
2322
2959
  exports.BaseAgent = BaseAgent;
2323
2960
  BaseAgent.MAX_VALIDATION_RETRIES = 10;