@memberjunction/ai-agents 2.107.0 → 2.108.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.
@@ -49,6 +49,7 @@ class BaseAgent {
49
49
  this._subAgentRuns = [];
50
50
  this._payloadManager = new PayloadManager_1.PayloadManager();
51
51
  this._validationRetryCount = 0;
52
+ this._contextRecoveryAttempted = false;
52
53
  }
53
54
  get AgentTypeState() {
54
55
  return this._agentTypeState;
@@ -158,6 +159,8 @@ class BaseAgent {
158
159
  await this.initializeAgentRun(wrappedParams);
159
160
  this._validationRetryCount = 0;
160
161
  this._generalValidationRetryCount = 0;
162
+ this._contextRecoveryAttempted = false;
163
+ this._messageLifecycleCallback = params.onMessageLifecycle;
161
164
  await this.initializeEngines(params.contextUser);
162
165
  if (params.cancellationToken?.aborted) {
163
166
  return await this.createCancelledResult('Cancelled during initialization', params.contextUser);
@@ -219,6 +222,7 @@ class BaseAgent {
219
222
  if (params.cancellationToken?.aborted) {
220
223
  throw new Error('Cancelled during execution');
221
224
  }
225
+ await this.pruneAndCompactExpiredMessages(params, stepCount);
222
226
  this.logStatus(`🔄 Executing step ${stepCount + 1} for agent '${params.agent.Name}'`, true, params);
223
227
  const nextStep = await this.executeNextStep(params, config, currentNextStep);
224
228
  stepCount++;
@@ -897,6 +901,163 @@ class BaseAgent {
897
901
  }
898
902
  return { exceeded: false };
899
903
  }
904
+ isFatalPromptError(promptResult) {
905
+ if (!promptResult?.chatResult?.errorInfo) {
906
+ return false;
907
+ }
908
+ const errorInfo = promptResult.chatResult.errorInfo;
909
+ if (errorInfo.severity === 'Fatal') {
910
+ return true;
911
+ }
912
+ const fatalErrorTypes = [
913
+ 'ContextLengthExceeded',
914
+ 'Authentication',
915
+ 'InvalidRequest'
916
+ ];
917
+ return fatalErrorTypes.includes(errorInfo.errorType);
918
+ }
919
+ contentToString(content) {
920
+ if (typeof content === 'string') {
921
+ return content;
922
+ }
923
+ return content.map((block) => {
924
+ if (block.type === 'text') {
925
+ return block.content;
926
+ }
927
+ return `[${block.type}: ${block.content}]`;
928
+ }).join('\n');
929
+ }
930
+ smartTrimContent(content, maxLength = 1000) {
931
+ const originalLength = content.length;
932
+ try {
933
+ const data = JSON.parse(content);
934
+ if (Array.isArray(data)) {
935
+ const itemsToKeep = 10;
936
+ if (data.length > itemsToKeep) {
937
+ const truncated = data.slice(0, itemsToKeep);
938
+ const trimmed = JSON.stringify(truncated, null, 2) +
939
+ `\n\n... (${(data.length - itemsToKeep).toLocaleString()} more items truncated due to context length)`;
940
+ return {
941
+ trimmed,
942
+ strategy: 'JSON array truncation',
943
+ originalLength
944
+ };
945
+ }
946
+ }
947
+ else if (typeof data === 'object' && data !== null) {
948
+ const truncated = {};
949
+ let fieldCount = 0;
950
+ const maxFields = 20;
951
+ for (const [key, value] of Object.entries(data)) {
952
+ if (fieldCount >= maxFields) {
953
+ truncated['...'] = `(${Object.keys(data).length - maxFields} more fields truncated)`;
954
+ break;
955
+ }
956
+ if (typeof value === 'string' && value.length > 200) {
957
+ truncated[key] = value.substring(0, 200) + '... (truncated)';
958
+ }
959
+ else if (Array.isArray(value) && value.length > 5) {
960
+ truncated[key] = [...value.slice(0, 5), `... (${value.length - 5} more items)`];
961
+ }
962
+ else {
963
+ truncated[key] = value;
964
+ }
965
+ fieldCount++;
966
+ }
967
+ return {
968
+ trimmed: JSON.stringify(truncated, null, 2),
969
+ strategy: 'JSON object field truncation',
970
+ originalLength
971
+ };
972
+ }
973
+ }
974
+ catch {
975
+ }
976
+ if (content.includes('\n')) {
977
+ const lines = content.split('\n');
978
+ const firstLine = lines[0];
979
+ if (firstLine.includes(',') && lines.length > 1) {
980
+ const rowsToKeep = 10;
981
+ if (lines.length > rowsToKeep + 1) {
982
+ const header = lines[0];
983
+ const dataRows = lines.slice(1, rowsToKeep + 1);
984
+ const trimmed = [header, ...dataRows].join('\n') +
985
+ `\n... (${(lines.length - rowsToKeep - 1).toLocaleString()} more rows truncated due to context length)`;
986
+ return {
987
+ trimmed,
988
+ strategy: 'CSV row truncation',
989
+ originalLength
990
+ };
991
+ }
992
+ }
993
+ }
994
+ if (content.length > maxLength) {
995
+ return {
996
+ trimmed: content.substring(0, maxLength) + '\n\n... (truncated due to context length)',
997
+ strategy: 'character truncation',
998
+ originalLength
999
+ };
1000
+ }
1001
+ return {
1002
+ trimmed: content,
1003
+ strategy: 'no truncation needed',
1004
+ originalLength
1005
+ };
1006
+ }
1007
+ async attemptContextRecovery(params, payload, errorMessage) {
1008
+ this.logStatus(`⚠️ Context length exceeded - attempting recovery by trimming conversation messages`, true, params);
1009
+ const messages = params.conversationMessages;
1010
+ let lastUserMessageIndex = -1;
1011
+ for (let i = messages.length - 1; i >= 0; i--) {
1012
+ if (messages[i].role === 'user') {
1013
+ lastUserMessageIndex = i;
1014
+ break;
1015
+ }
1016
+ }
1017
+ if (lastUserMessageIndex === -1) {
1018
+ this.logStatus(`❌ Context recovery failed: No trimmable user messages found`, true, params);
1019
+ return {
1020
+ errorMessage: `Context overflow with no trimmable messages: ${errorMessage}`,
1021
+ step: 'Failed',
1022
+ terminate: true,
1023
+ previousPayload: payload,
1024
+ newPayload: payload
1025
+ };
1026
+ }
1027
+ const originalMessage = messages[lastUserMessageIndex];
1028
+ const contentString = this.contentToString(originalMessage.content);
1029
+ const trimResult = this.smartTrimContent(contentString, 1000);
1030
+ this.logStatus(`✂️ Trimmed message from ${trimResult.originalLength.toLocaleString()} to ${trimResult.trimmed.length.toLocaleString()} characters using ${trimResult.strategy}`, true, params);
1031
+ messages[lastUserMessageIndex] = {
1032
+ role: 'user',
1033
+ content: `⚠️ CONTEXT OVERFLOW RECOVERY ⚠️
1034
+
1035
+ The previous step returned a result that exceeded the context window (${(trimResult.originalLength - trimResult.trimmed.length).toLocaleString()} characters truncated).
1036
+
1037
+ Here is a PARTIAL result from the previous action:
1038
+ ---
1039
+ ${trimResult.trimmed}
1040
+ ---
1041
+
1042
+ ❗ THE ABOVE IS INCOMPLETE - the full result was too large for the context window.
1043
+
1044
+ RECOMMENDED ACTIONS:
1045
+ 1. Use a different action with more specific filters to get smaller result sets
1046
+ 2. Request data in batches or pages instead of all at once
1047
+ 3. Ask the user to clarify scope to narrow the query
1048
+ 4. If you need the full data, acknowledge the limitation and ask the user how to proceed
1049
+
1050
+ Please choose an alternative approach to complete your task.`
1051
+ };
1052
+ return {
1053
+ step: 'Retry',
1054
+ retryReason: 'Context length recovery - previous result too large',
1055
+ retryInstructions: 'The previous action result exceeded context limits. Use more specific filters or request data in smaller batches.',
1056
+ terminate: false,
1057
+ previousPayload: payload,
1058
+ newPayload: payload
1059
+ };
1060
+ }
900
1061
  async processNextStep(nextStep, params, agentType, promptResult, currentPayload, currentStep) {
901
1062
  const validatedNextStep = await this.validateNextStep(params, nextStep, currentPayload, this._agentRun, currentStep);
902
1063
  const guardrailCheckedStep = await this.checkExecutionGuardrails(params, validatedNextStep, currentPayload, this._agentRun, currentStep);
@@ -1006,16 +1167,18 @@ class BaseAgent {
1006
1167
  throw new Error(`Error executing actions: ${error.message}`);
1007
1168
  }
1008
1169
  }
1009
- async ExecuteSubAgent(params, subAgentRequest, subAgent, stepEntity, payload) {
1170
+ async ExecuteSubAgent(params, subAgentRequest, subAgent, stepEntity, payload, contextMessage) {
1010
1171
  try {
1011
1172
  this.logStatus(`🤖 Executing sub-agent '${subAgentRequest.name}'`, true, params);
1012
1173
  const runner = new AgentRunner_1.AgentRunner();
1013
- const subAgentMessages = [
1014
- {
1015
- role: 'user',
1016
- content: subAgentRequest.message
1017
- }
1018
- ];
1174
+ const subAgentMessages = [];
1175
+ if (contextMessage) {
1176
+ subAgentMessages.push(contextMessage);
1177
+ }
1178
+ subAgentMessages.push({
1179
+ role: 'user',
1180
+ content: subAgentRequest.message
1181
+ });
1019
1182
  this.logStatus(`📨 Sub-agent message: "${subAgentRequest.message}"`, true, params);
1020
1183
  if (subAgentRequest.templateParameters) {
1021
1184
  this.logStatus(`📎 Template parameters: ${JSON.stringify(subAgentRequest.templateParameters)}`, true, params);
@@ -1134,6 +1297,25 @@ class BaseAgent {
1134
1297
  ValueType: param.ValueType
1135
1298
  };
1136
1299
  }
1300
+ formatParamValueForMessage(value, maxLength = 100) {
1301
+ if (value === null || value === undefined) {
1302
+ return 'null';
1303
+ }
1304
+ let stringValue;
1305
+ if (typeof value === 'string') {
1306
+ stringValue = value;
1307
+ }
1308
+ else if (typeof value === 'object') {
1309
+ stringValue = JSON.stringify(value);
1310
+ }
1311
+ else {
1312
+ stringValue = String(value);
1313
+ }
1314
+ if (stringValue.length > maxLength) {
1315
+ return `\`${stringValue.substring(0, maxLength)}...\``;
1316
+ }
1317
+ return `\`${stringValue}\``;
1318
+ }
1137
1319
  getAgentTypeName(typeID) {
1138
1320
  const agentType = aiengine_1.AIEngine.Instance.AgentTypes.find(at => at.ID === typeID);
1139
1321
  return agentType?.Name || 'Unknown';
@@ -1364,6 +1546,10 @@ class BaseAgent {
1364
1546
  }
1365
1547
  switch (previousDecision.step) {
1366
1548
  case 'Retry':
1549
+ if (previousDecision.messageIndex !== undefined) {
1550
+ const currentStepCount = this._agentRun?.Steps?.length || 0;
1551
+ this.executeExpandMessageStep(previousDecision, params, currentStepCount);
1552
+ }
1367
1553
  return await this.executePromptStep(params, config, previousDecision);
1368
1554
  case 'Sub-Agent':
1369
1555
  return await this.processSubAgentStep(params, previousDecision);
@@ -1466,6 +1652,25 @@ class BaseAgent {
1466
1652
  stepEntity.TargetLogID = promptResult.promptRun.ID;
1467
1653
  stepEntity.PromptRun = promptResult.promptRun;
1468
1654
  }
1655
+ if (!promptResult.success) {
1656
+ await this.finalizeStepEntity(stepEntity, false, promptResult.errorMessage);
1657
+ const isFatal = this.isFatalPromptError(promptResult);
1658
+ const isContextOverflow = isFatal &&
1659
+ promptResult.chatResult?.errorInfo?.errorType === 'ContextLengthExceeded';
1660
+ if (isContextOverflow && !this._contextRecoveryAttempted) {
1661
+ this._contextRecoveryAttempted = true;
1662
+ this.logStatus(`⚠️ Context length exceeded - attempting recovery by trimming conversation`, true, params);
1663
+ return await this.attemptContextRecovery(params, payload, promptResult.errorMessage || 'Context length exceeded');
1664
+ }
1665
+ this.logStatus(`❌ Prompt execution failed: ${promptResult.errorMessage} (fatal: ${isFatal})`, true, params);
1666
+ return {
1667
+ errorMessage: promptResult.errorMessage || 'Prompt execution failed',
1668
+ step: 'Failed',
1669
+ terminate: isFatal,
1670
+ previousPayload: payload,
1671
+ newPayload: payload
1672
+ };
1673
+ }
1469
1674
  if (params.cancellationToken?.aborted) {
1470
1675
  await this.finalizeStepEntity(stepEntity, false, 'Cancelled during prompt execution');
1471
1676
  const cancelledResult = await this.createCancelledResult('Cancelled during prompt execution', params.contextUser);
@@ -1631,7 +1836,7 @@ class BaseAgent {
1631
1836
  }
1632
1837
  }
1633
1838
  stepEntity.PayloadAtStart = JSON.stringify(previousDecision.newPayload);
1634
- const subAgentResult = await this.ExecuteSubAgent(params, subAgentRequest, subAgentEntity, stepEntity, scopedPayload);
1839
+ const subAgentResult = await this.ExecuteSubAgent(params, subAgentRequest, subAgentEntity, stepEntity, scopedPayload, undefined);
1635
1840
  let mergedPayload = previousDecision.newPayload;
1636
1841
  let currentStepPayloadChangeResult = undefined;
1637
1842
  if (subAgentResult.success) {
@@ -1843,12 +2048,34 @@ class BaseAgent {
1843
2048
  this.incrementExecutionCount(subAgentEntity.ID);
1844
2049
  try {
1845
2050
  stepEntity.PayloadAtStart = JSON.stringify(previousDecision.newPayload);
1846
- const subAgentResult = await this.ExecuteSubAgent(params, subAgentRequest, subAgentEntity, stepEntity, undefined);
2051
+ let initialSubAgentPayload = undefined;
2052
+ if (relationship.SubAgentInputMapping) {
2053
+ const mapped = this.applySubAgentInputMapping(previousDecision.newPayload, relationship.SubAgentInputMapping);
2054
+ if (mapped && Object.keys(mapped).length > 0) {
2055
+ initialSubAgentPayload = mapped;
2056
+ if (params.verbose === true || (0, core_1.IsVerboseLoggingEnabled)()) {
2057
+ (0, core_1.LogStatus)(`Related sub-agent '${subAgentRequest.name}' receiving mapped payload: ${JSON.stringify(Object.keys(mapped))}`);
2058
+ }
2059
+ }
2060
+ }
2061
+ let contextPaths = [];
2062
+ if (relationship.SubAgentContextPaths) {
2063
+ try {
2064
+ contextPaths = JSON.parse(relationship.SubAgentContextPaths);
2065
+ }
2066
+ catch (parseError) {
2067
+ (0, core_1.LogError)(`Failed to parse SubAgentContextPaths for sub-agent ${subAgentRequest.name}: ${parseError.message}`);
2068
+ }
2069
+ }
2070
+ const contextMessage = this.prepareRelatedSubAgentContextMessage(previousDecision.newPayload, contextPaths);
2071
+ if (contextMessage && (params.verbose === true || (0, core_1.IsVerboseLoggingEnabled)())) {
2072
+ (0, core_1.LogStatus)(`Related sub-agent '${subAgentRequest.name}' receiving context from paths: ${contextPaths.join(', ')}`);
2073
+ }
2074
+ const subAgentResult = await this.ExecuteSubAgent(params, subAgentRequest, subAgentEntity, stepEntity, initialSubAgentPayload, contextMessage);
1847
2075
  let mergedPayload = previousDecision.newPayload;
1848
2076
  let currentStepPayloadChangeResult = undefined;
1849
- const relationshipWithMapping = relationship;
1850
- if (subAgentResult.success && relationshipWithMapping.SubAgentOutputMapping) {
1851
- const payloadChange = this.applySubAgentOutputMapping(subAgentResult.payload, previousDecision.newPayload, relationshipWithMapping.SubAgentOutputMapping);
2077
+ if (subAgentResult.success && relationship.SubAgentOutputMapping) {
2078
+ const payloadChange = this.applySubAgentOutputMapping(subAgentResult.payload, previousDecision.newPayload, relationship.SubAgentOutputMapping);
1852
2079
  if (payloadChange && payloadChange.updateElements) {
1853
2080
  const mergeResult = this._payloadManager.applyAgentChangeRequest(previousDecision.newPayload, payloadChange, {
1854
2081
  validateChanges: true,
@@ -1872,7 +2099,7 @@ class BaseAgent {
1872
2099
  finalStep: subAgentResult.agentRun?.FinalStep,
1873
2100
  errorMessage: subAgentResult.agentRun?.ErrorMessage,
1874
2101
  stepCount: subAgentResult.agentRun?.Steps?.length || 0,
1875
- hasMergedPayload: !!(relationshipWithMapping.SubAgentOutputMapping && mergedPayload !== previousDecision.newPayload)
2102
+ hasMergedPayload: !!(relationship.SubAgentOutputMapping && mergedPayload !== previousDecision.newPayload)
1876
2103
  },
1877
2104
  shouldTerminate: shouldTerminate,
1878
2105
  nextStep: shouldTerminate ? 'success' : 'retry',
@@ -1880,6 +2107,9 @@ class BaseAgent {
1880
2107
  payloadChangeResult: currentStepPayloadChangeResult
1881
2108
  })
1882
2109
  };
2110
+ if (stepEntity) {
2111
+ stepEntity.PayloadAtEnd = JSON.stringify(mergedPayload);
2112
+ }
1883
2113
  await this.finalizeStepEntity(stepEntity, subAgentResult.success, subAgentResult.agentRun?.ErrorMessage, outputData);
1884
2114
  if (subAgentResult.agentRun?.FinalStep === 'Chat') {
1885
2115
  return {
@@ -1901,9 +2131,6 @@ class BaseAgent {
1901
2131
  role: 'user',
1902
2132
  content: `Related sub-agent "${subAgentRequest.name}" completed:\n${JSON.stringify(subAgentSummary, null, 2)}`
1903
2133
  });
1904
- if (stepEntity) {
1905
- stepEntity.PayloadAtEnd = JSON.stringify(mergedPayload);
1906
- }
1907
2134
  if (this._agentRun) {
1908
2135
  this._agentRun.FinalPayloadObject = mergedPayload;
1909
2136
  }
@@ -1965,6 +2192,72 @@ class BaseAgent {
1965
2192
  return null;
1966
2193
  }
1967
2194
  }
2195
+ applySubAgentInputMapping(parentPayload, mappingConfig) {
2196
+ try {
2197
+ const mapping = JSON.parse(mappingConfig);
2198
+ const subAgentPayload = {};
2199
+ for (const [parentPath, subAgentPath] of Object.entries(mapping)) {
2200
+ let value;
2201
+ if (parentPath === '*') {
2202
+ value = parentPayload;
2203
+ }
2204
+ else {
2205
+ value = this.getValueFromPath(parentPayload, parentPath);
2206
+ }
2207
+ if (value !== undefined) {
2208
+ const pathParts = subAgentPath.split('.');
2209
+ let current = subAgentPayload;
2210
+ for (let i = 0; i < pathParts.length - 1; i++) {
2211
+ const part = pathParts[i];
2212
+ if (!(part in current)) {
2213
+ current[part] = {};
2214
+ }
2215
+ current = current[part];
2216
+ }
2217
+ current[pathParts[pathParts.length - 1]] = value;
2218
+ }
2219
+ }
2220
+ if (Object.keys(subAgentPayload).length === 0) {
2221
+ return null;
2222
+ }
2223
+ return subAgentPayload;
2224
+ }
2225
+ catch (error) {
2226
+ (0, core_1.LogError)(`Failed to parse SubAgentInputMapping: ${error.message}`);
2227
+ return null;
2228
+ }
2229
+ }
2230
+ prepareRelatedSubAgentContextMessage(parentPayload, contextPaths) {
2231
+ if (!contextPaths || contextPaths.length === 0) {
2232
+ return null;
2233
+ }
2234
+ if (contextPaths.includes('*')) {
2235
+ return {
2236
+ role: 'user',
2237
+ content: `Parent Agent Context:\n\n${JSON.stringify(parentPayload, null, 2)}`
2238
+ };
2239
+ }
2240
+ const contextData = {};
2241
+ for (const path of contextPaths) {
2242
+ const value = this.getValueFromPath(parentPayload, path);
2243
+ if (value !== undefined) {
2244
+ contextData[path] = value;
2245
+ }
2246
+ }
2247
+ if (Object.keys(contextData).length === 0) {
2248
+ return null;
2249
+ }
2250
+ const contextLines = Object.entries(contextData).map(([key, value]) => {
2251
+ const valueStr = typeof value === 'object'
2252
+ ? JSON.stringify(value, null, 2)
2253
+ : String(value);
2254
+ return `${key}:\n${valueStr}`;
2255
+ });
2256
+ return {
2257
+ role: 'user',
2258
+ content: `Parent Agent Context:\n\n${contextLines.join('\n\n')}`
2259
+ };
2260
+ }
1968
2261
  getValueFromPath(obj, path) {
1969
2262
  const parts = path.split('.');
1970
2263
  let current = obj;
@@ -2009,35 +2302,34 @@ class BaseAgent {
2009
2302
  let progressMessage;
2010
2303
  if (actions.length === 1) {
2011
2304
  const aa = actions[0];
2012
- progressMessage = `Executing action: **${aa.name}**`;
2305
+ progressMessage = `Executing **${aa.name}** action`;
2013
2306
  if (aa.params && Object.keys(aa.params).length > 0) {
2014
2307
  const paramsList = Object.entries(aa.params)
2015
2308
  .map(([key, value]) => {
2016
- const displayValue = typeof value === 'object'
2017
- ? JSON.stringify(value, null, 2)
2018
- : String(value);
2019
- return ` - \`${key}\`: ${displayValue}`;
2309
+ const displayValue = this.formatParamValueForMessage(value);
2310
+ return `• **${key}**: ${displayValue}`;
2020
2311
  })
2021
2312
  .join('\n');
2022
- progressMessage += `\n${paramsList}`;
2313
+ progressMessage += ` with parameters:\n${paramsList}`;
2314
+ }
2315
+ else {
2316
+ progressMessage += '.';
2023
2317
  }
2024
2318
  }
2025
2319
  else {
2026
- progressMessage = `Executing ${actions.length} actions:`;
2027
- actions.forEach(aa => {
2028
- progressMessage += `\n\n• **${aa.name}**`;
2320
+ progressMessage = `Executing **${actions.length} actions** in parallel:\n\n` + actions.map((aa, index) => {
2321
+ let actionText = `${index + 1}. **${aa.name}**`;
2029
2322
  if (aa.params && Object.keys(aa.params).length > 0) {
2030
2323
  const paramsList = Object.entries(aa.params)
2031
2324
  .map(([key, value]) => {
2032
- const displayValue = typeof value === 'object'
2033
- ? JSON.stringify(value, null, 2)
2034
- : String(value);
2035
- return ` - \`${key}\`: ${displayValue}`;
2325
+ const displayValue = this.formatParamValueForMessage(value);
2326
+ return ` • **${key}**: ${displayValue}`;
2036
2327
  })
2037
2328
  .join('\n');
2038
- progressMessage += `\n${paramsList}`;
2329
+ actionText += `\n${paramsList}`;
2039
2330
  }
2040
- });
2331
+ return actionText;
2332
+ }).join('\n\n');
2041
2333
  }
2042
2334
  params.onProgress?.({
2043
2335
  step: 'action_execution',
@@ -2049,9 +2341,38 @@ class BaseAgent {
2049
2341
  },
2050
2342
  displayMode: 'live'
2051
2343
  });
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')}`;
2344
+ let actionMessage;
2345
+ if (actions.length === 1) {
2346
+ const aa = actions[0];
2347
+ actionMessage = `I'm executing the **${aa.name}** action`;
2348
+ if (aa.params && Object.keys(aa.params).length > 0) {
2349
+ const paramsList = Object.entries(aa.params)
2350
+ .map(([key, value]) => {
2351
+ const displayValue = this.formatParamValueForMessage(value);
2352
+ return `• **${key}**: ${displayValue}`;
2353
+ })
2354
+ .join('\n');
2355
+ actionMessage += ` with parameters:\n${paramsList}`;
2356
+ }
2357
+ else {
2358
+ actionMessage += '.';
2359
+ }
2360
+ }
2361
+ else {
2362
+ actionMessage = `I'm executing **${actions.length} actions** in parallel:\n\n` + actions.map((aa, index) => {
2363
+ let actionText = `${index + 1}. **${aa.name}**`;
2364
+ if (aa.params && Object.keys(aa.params).length > 0) {
2365
+ const paramsList = Object.entries(aa.params)
2366
+ .map(([key, value]) => {
2367
+ const displayValue = this.formatParamValueForMessage(value);
2368
+ return ` • **${key}**: ${displayValue}`;
2369
+ })
2370
+ .join('\n');
2371
+ actionText += `\n${paramsList}`;
2372
+ }
2373
+ return actionText;
2374
+ }).join('\n\n');
2375
+ }
2055
2376
  params.conversationMessages.push({
2056
2377
  role: 'assistant',
2057
2378
  content: actionMessage
@@ -2120,9 +2441,44 @@ class BaseAgent {
2120
2441
  });
2121
2442
  const failedActions = actionSummaries.filter(a => !a.success);
2122
2443
  const resultsMessage = (failedActions.length > 0 ? `${failedActions.length} of ${actionSummaries.length} failed:` : `Action results:`) + `\n${JSON.stringify(actionSummaries, null, 2)}`;
2444
+ let metadata;
2445
+ const agentActionConfigs = actionResults
2446
+ .map(r => agentActions.find(aa => aa.ActionID === r.actionEntity.ID))
2447
+ .filter(aa => aa != null);
2448
+ if (agentActionConfigs.length > 0) {
2449
+ let minExpirationTurns = null;
2450
+ let expirationMode = 'None';
2451
+ let compactMode;
2452
+ let compactLength;
2453
+ let compactPromptId;
2454
+ for (const agentAction of agentActionConfigs) {
2455
+ if (agentAction.ResultExpirationTurns != null) {
2456
+ if (minExpirationTurns === null || agentAction.ResultExpirationTurns < minExpirationTurns) {
2457
+ minExpirationTurns = agentAction.ResultExpirationTurns;
2458
+ expirationMode = agentAction.ResultExpirationMode || 'None';
2459
+ compactMode = agentAction.CompactMode;
2460
+ compactLength = agentAction.CompactLength;
2461
+ compactPromptId = agentAction.CompactPromptID;
2462
+ }
2463
+ }
2464
+ }
2465
+ if (minExpirationTurns !== null && expirationMode !== 'None') {
2466
+ const currentStepCount = this._agentRun?.Steps?.length || 0;
2467
+ metadata = {
2468
+ turnAdded: currentStepCount,
2469
+ messageType: 'action-result',
2470
+ expirationTurns: minExpirationTurns,
2471
+ expirationMode: expirationMode,
2472
+ compactMode: compactMode,
2473
+ compactLength: compactLength,
2474
+ compactPromptId: compactPromptId
2475
+ };
2476
+ }
2477
+ }
2123
2478
  params.conversationMessages.push({
2124
2479
  role: 'user',
2125
- content: resultsMessage
2480
+ content: resultsMessage,
2481
+ metadata: metadata
2126
2482
  });
2127
2483
  let finalPayload = currentPayload;
2128
2484
  try {
@@ -2188,6 +2544,7 @@ class BaseAgent {
2188
2544
  confidence: previousDecision.confidence,
2189
2545
  previousPayload: previousDecision.previousPayload,
2190
2546
  newPayload: previousDecision.newPayload || previousDecision.previousPayload,
2547
+ suggestedResponses: previousDecision.suggestedResponses
2191
2548
  };
2192
2549
  }
2193
2550
  async createFailureResult(errorMessage, contextUser) {
@@ -2257,7 +2614,8 @@ class BaseAgent {
2257
2614
  return {
2258
2615
  success: finalStep.step === 'Success' || finalStep.step === 'Chat',
2259
2616
  payload,
2260
- agentRun: this._agentRun
2617
+ agentRun: this._agentRun,
2618
+ suggestedResponses: finalStep.suggestedResponses
2261
2619
  };
2262
2620
  }
2263
2621
  calculateTokenStats() {
@@ -2318,6 +2676,236 @@ class BaseAgent {
2318
2676
  }
2319
2677
  return violations;
2320
2678
  }
2679
+ async pruneAndCompactExpiredMessages(params, currentTurn) {
2680
+ const messagesToCompact = [];
2681
+ const messagesToRemove = [];
2682
+ for (let i = 0; i < params.conversationMessages.length; i++) {
2683
+ const msg = params.conversationMessages[i];
2684
+ if (!msg.metadata?.expirationTurns && msg.metadata?.expirationTurns !== 0) {
2685
+ continue;
2686
+ }
2687
+ if (msg.metadata.expirationMode === 'None') {
2688
+ continue;
2689
+ }
2690
+ const turnAdded = msg.metadata.turnAdded || 0;
2691
+ const turnsAlive = currentTurn - turnAdded;
2692
+ if (turnsAlive > msg.metadata.expirationTurns) {
2693
+ msg.metadata.isExpired = true;
2694
+ if (msg.metadata.expirationMode === 'Remove') {
2695
+ messagesToRemove.push(i);
2696
+ this.emitMessageLifecycleEvent({
2697
+ type: 'message-expired',
2698
+ turn: currentTurn,
2699
+ messageIndex: i,
2700
+ message: msg,
2701
+ reason: `Expired after ${turnsAlive} turns (limit: ${msg.metadata.expirationTurns})`
2702
+ });
2703
+ }
2704
+ else if (msg.metadata.expirationMode === 'Compact') {
2705
+ if (msg.metadata.compactMode) {
2706
+ messagesToCompact.push({
2707
+ index: i,
2708
+ message: msg,
2709
+ metadata: {
2710
+ compactMode: msg.metadata.compactMode,
2711
+ compactLength: msg.metadata.compactLength || 500,
2712
+ compactPromptId: msg.metadata.compactPromptId || '',
2713
+ originalLength: typeof msg.content === 'string'
2714
+ ? msg.content.length
2715
+ : JSON.stringify(msg.content).length
2716
+ }
2717
+ });
2718
+ }
2719
+ }
2720
+ }
2721
+ }
2722
+ const preserveOriginal = params.messageExpirationOverride?.preserveOriginalContent !== false;
2723
+ for (const item of messagesToCompact) {
2724
+ const originalContent = item.message.content;
2725
+ const compacted = await this.compactMessage(item.message, item.metadata, params);
2726
+ const originalTokens = this.estimateTokens(originalContent);
2727
+ const compactedTokens = this.estimateTokens(compacted);
2728
+ const tokensSaved = originalTokens - compactedTokens;
2729
+ params.conversationMessages[item.index] = {
2730
+ ...item.message,
2731
+ content: compacted,
2732
+ metadata: {
2733
+ ...item.message.metadata,
2734
+ wasCompacted: true,
2735
+ originalContent: preserveOriginal ? originalContent : undefined,
2736
+ originalLength: item.metadata.originalLength,
2737
+ tokensSaved,
2738
+ canExpand: preserveOriginal
2739
+ }
2740
+ };
2741
+ this.emitMessageLifecycleEvent({
2742
+ type: 'message-compacted',
2743
+ turn: currentTurn,
2744
+ messageIndex: item.index,
2745
+ message: params.conversationMessages[item.index],
2746
+ reason: `Compacted using ${item.metadata.compactMode} (saved ${tokensSaved} tokens)`,
2747
+ tokensSaved
2748
+ });
2749
+ }
2750
+ for (let i = messagesToRemove.length - 1; i >= 0; i--) {
2751
+ const index = messagesToRemove[i];
2752
+ const removed = params.conversationMessages.splice(index, 1)[0];
2753
+ this.emitMessageLifecycleEvent({
2754
+ type: 'message-removed',
2755
+ turn: currentTurn,
2756
+ messageIndex: index,
2757
+ message: removed,
2758
+ reason: 'Removed due to expiration'
2759
+ });
2760
+ }
2761
+ if (params.verbose && (messagesToCompact.length > 0 || messagesToRemove.length > 0)) {
2762
+ const totalSaved = messagesToCompact.reduce((sum, item) => {
2763
+ const msg = params.conversationMessages[item.index];
2764
+ return sum + (msg.metadata?.tokensSaved || 0);
2765
+ }, 0);
2766
+ console.log(`[Turn ${currentTurn}] Message pruning: ` +
2767
+ `${messagesToCompact.length} compacted (saved ~${totalSaved} tokens), ` +
2768
+ `${messagesToRemove.length} removed`);
2769
+ }
2770
+ }
2771
+ async createCompactionStep(prompt, message, params) {
2772
+ if (!this._agentRun) {
2773
+ throw new Error('Cannot create compaction step: agent run not initialized');
2774
+ }
2775
+ const md = new core_1.Metadata();
2776
+ const step = await md.GetEntityObject('MJ: AI Agent Run Steps', params.contextUser);
2777
+ step.NewRecord();
2778
+ step.AgentRunID = this._agentRun.ID;
2779
+ step.StepType = 'Prompt';
2780
+ step.Status = 'Running';
2781
+ step.InputData = JSON.stringify({
2782
+ stepName: 'Message Compaction',
2783
+ description: `Compacting message using AI Summary (${message.metadata?.messageType || 'unknown'} from turn ${message.metadata?.turnAdded || 0})`,
2784
+ messageType: message.metadata?.messageType,
2785
+ turnAdded: message.metadata?.turnAdded,
2786
+ originalLength: message.metadata?.originalLength ||
2787
+ (typeof message.content === 'string' ? message.content.length : JSON.stringify(message.content).length),
2788
+ compactMode: 'AI Summary',
2789
+ promptId: prompt.ID,
2790
+ promptName: prompt.Name
2791
+ });
2792
+ await step.Save();
2793
+ return step;
2794
+ }
2795
+ async updateCompactionStep(step, result, message, params) {
2796
+ step.Status = result.success ? 'Completed' : 'Failed';
2797
+ const promptTokens = result.promptTokens || 0;
2798
+ const completionTokens = result.completionTokens || 0;
2799
+ step.OutputData = JSON.stringify({
2800
+ success: result.success,
2801
+ summaryLength: result.result?.summary?.length || 0,
2802
+ tokensSaved: message.metadata?.tokensSaved || 0,
2803
+ promptTokens,
2804
+ completionTokens,
2805
+ totalTokens: promptTokens + completionTokens,
2806
+ cost: result.cost
2807
+ });
2808
+ if (!result.success) {
2809
+ step.ErrorMessage = result.errorMessage || 'AI Summary compaction failed';
2810
+ }
2811
+ await step.Save();
2812
+ }
2813
+ async compactMessage(message, metadata, params) {
2814
+ const originalContent = typeof message.content === 'string'
2815
+ ? message.content
2816
+ : JSON.stringify(message.content);
2817
+ switch (metadata.compactMode) {
2818
+ case 'First N Chars': {
2819
+ const length = metadata.compactLength;
2820
+ if (originalContent.length <= length) {
2821
+ return originalContent;
2822
+ }
2823
+ const truncated = originalContent.substring(0, length);
2824
+ return `${truncated}...\n\n[Compacted: showing first ${length} of ${originalContent.length} characters. Agent can request expansion if needed.]`;
2825
+ }
2826
+ case 'AI Summary': {
2827
+ try {
2828
+ const promptId = metadata.compactPromptId || this.getSystemDefaultCompactPromptId();
2829
+ const prompt = aiengine_1.AIEngine.Instance.Prompts.find(p => p.ID === promptId);
2830
+ if (!prompt) {
2831
+ console.warn(`Compact prompt ${promptId} not found, falling back to First N Chars`);
2832
+ return this.compactMessage(message, { ...metadata, compactMode: 'First N Chars' }, params);
2833
+ }
2834
+ const step = await this.createCompactionStep(prompt, message, params);
2835
+ const promptParams = new ai_core_plus_1.AIPromptParams();
2836
+ promptParams.prompt = prompt;
2837
+ promptParams.data = {
2838
+ originalContent,
2839
+ originalLength: metadata.originalLength,
2840
+ targetLength: metadata.compactLength || 500,
2841
+ messageType: message.metadata?.messageType || 'unknown',
2842
+ turnAdded: message.metadata?.turnAdded || 0
2843
+ };
2844
+ promptParams.contextUser = params.contextUser;
2845
+ const runner = new ai_prompts_1.AIPromptRunner();
2846
+ const result = await runner.ExecutePrompt(promptParams);
2847
+ await this.updateCompactionStep(step, result, message, params);
2848
+ if (!result.success || !result.result?.summary) {
2849
+ console.warn('AI summary failed, falling back to First N Chars');
2850
+ return this.compactMessage(message, { ...metadata, compactMode: 'First N Chars' }, params);
2851
+ }
2852
+ return `[AI Summary of ${metadata.originalLength} chars. Agent can request full expansion if needed.]\n\n${result.result.summary}`;
2853
+ }
2854
+ catch (error) {
2855
+ console.error('Error during AI summary:', error);
2856
+ return this.compactMessage(message, { ...metadata, compactMode: 'First N Chars' }, params);
2857
+ }
2858
+ }
2859
+ default:
2860
+ return originalContent;
2861
+ }
2862
+ }
2863
+ getSystemDefaultCompactPromptId() {
2864
+ const prompt = aiengine_1.AIEngine.Instance.Prompts.find(p => p.Name === 'Compact Agent Message');
2865
+ if (!prompt) {
2866
+ console.warn('System default compact prompt not found. Ensure "Compact Agent Message" prompt exists.');
2867
+ return '';
2868
+ }
2869
+ return prompt.ID;
2870
+ }
2871
+ estimateTokens(content) {
2872
+ const text = typeof content === 'string'
2873
+ ? content
2874
+ : JSON.stringify(content);
2875
+ return Math.ceil(text.length / 4);
2876
+ }
2877
+ emitMessageLifecycleEvent(event) {
2878
+ if (this._messageLifecycleCallback) {
2879
+ this._messageLifecycleCallback(event);
2880
+ }
2881
+ }
2882
+ executeExpandMessageStep(request, params, currentTurn) {
2883
+ const messageIndex = request.messageIndex;
2884
+ const reason = request.expandReason;
2885
+ if (messageIndex === undefined || messageIndex < 0 || messageIndex >= params.conversationMessages.length) {
2886
+ console.warn(`Cannot expand message: index ${messageIndex} out of bounds`);
2887
+ return;
2888
+ }
2889
+ const message = params.conversationMessages[messageIndex];
2890
+ if (!message.metadata?.canExpand || !message.metadata?.originalContent) {
2891
+ console.warn(`Cannot expand message at index ${messageIndex}: not expandable or no original content`);
2892
+ return;
2893
+ }
2894
+ message.content = message.metadata.originalContent;
2895
+ message.metadata.wasCompacted = false;
2896
+ message.metadata.canExpand = false;
2897
+ delete message.metadata.originalContent;
2898
+ this.emitMessageLifecycleEvent({
2899
+ type: 'message-expanded',
2900
+ turn: currentTurn,
2901
+ messageIndex,
2902
+ message,
2903
+ reason: reason || 'Agent requested expansion'
2904
+ });
2905
+ if (params.verbose) {
2906
+ console.log(`[Turn ${currentTurn}] Expanded message at index ${messageIndex}`);
2907
+ }
2908
+ }
2321
2909
  }
2322
2910
  exports.BaseAgent = BaseAgent;
2323
2911
  BaseAgent.MAX_VALIDATION_RETRIES = 10;