@memberjunction/ai-agents 2.106.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,9 +1546,13 @@ 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
- return await this.executeSubAgentStep(params, previousDecision);
1555
+ return await this.processSubAgentStep(params, previousDecision);
1370
1556
  case 'Actions':
1371
1557
  return await this.executeActionsStep(params, config, previousDecision);
1372
1558
  case 'Chat':
@@ -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);
@@ -1555,7 +1760,7 @@ class BaseAgent {
1555
1760
  return errorNextStep;
1556
1761
  }
1557
1762
  }
1558
- async executeSubAgentStep(params, previousDecision) {
1763
+ async executeChildSubAgentStep(params, previousDecision) {
1559
1764
  const subAgentRequest = previousDecision.subAgent;
1560
1765
  if (params.cancellationToken?.aborted) {
1561
1766
  throw new Error('Cancelled before sub-agent execution');
@@ -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) {
@@ -1771,6 +1976,322 @@ class BaseAgent {
1771
1976
  };
1772
1977
  }
1773
1978
  }
1979
+ async processSubAgentStep(params, previousDecision) {
1980
+ const subAgentRequest = previousDecision.subAgent;
1981
+ const name = subAgentRequest?.name;
1982
+ if (!name) {
1983
+ return {
1984
+ step: 'Failed',
1985
+ terminate: false,
1986
+ errorMessage: 'Sub-agent name is required',
1987
+ previousPayload: previousDecision?.newPayload,
1988
+ newPayload: previousDecision?.newPayload
1989
+ };
1990
+ }
1991
+ const childAgents = aiengine_1.AIEngine.Instance.Agents.filter(a => a.ParentID === params.agent.ID &&
1992
+ a.Status === 'Active');
1993
+ const childAgent = childAgents.find(a => a.Name.trim().toLowerCase() === name.trim().toLowerCase());
1994
+ if (childAgent) {
1995
+ return await this.executeChildSubAgentStep(params, previousDecision);
1996
+ }
1997
+ const activeRelationships = aiengine_1.AIEngine.Instance.AgentRelationships.filter(ar => ar.AgentID === params.agent.ID &&
1998
+ ar.Status === 'Active');
1999
+ for (const relationship of activeRelationships) {
2000
+ const relatedAgent = aiengine_1.AIEngine.Instance.Agents.find(a => a.ID === relationship.SubAgentID &&
2001
+ a.Status === 'Active');
2002
+ if (relatedAgent && relatedAgent.Name.trim().toLowerCase() === name.trim().toLowerCase()) {
2003
+ return await this.executeRelatedSubAgentStep(params, previousDecision, relatedAgent, relationship);
2004
+ }
2005
+ }
2006
+ this.logError(`Sub-agent '${name}' not found or not active for agent '${params.agent.Name}'`, {
2007
+ agent: params.agent,
2008
+ category: 'SubAgentExecution'
2009
+ });
2010
+ return {
2011
+ step: 'Retry',
2012
+ terminate: false,
2013
+ errorMessage: `Sub-agent '${name}' not found or not active`,
2014
+ previousPayload: previousDecision?.newPayload,
2015
+ newPayload: previousDecision?.newPayload
2016
+ };
2017
+ }
2018
+ async executeRelatedSubAgentStep(params, previousDecision, subAgentEntity, relationship) {
2019
+ const subAgentRequest = previousDecision.subAgent;
2020
+ if (params.cancellationToken?.aborted) {
2021
+ throw new Error('Cancelled before related sub-agent execution');
2022
+ }
2023
+ params.onProgress?.({
2024
+ step: 'subagent_execution',
2025
+ percentage: 60,
2026
+ message: this.formatHierarchicalMessage(`Delegating to ${subAgentRequest.name} agent`),
2027
+ metadata: {
2028
+ agentName: params.agent.Name,
2029
+ subAgentName: subAgentRequest.name,
2030
+ reason: subAgentRequest.message,
2031
+ relationshipType: 'related'
2032
+ }
2033
+ });
2034
+ params.conversationMessages.push({
2035
+ role: 'assistant',
2036
+ content: `I'm delegating this task to the "${subAgentRequest.name}" agent.\n\nReason: ${subAgentRequest.message}`
2037
+ });
2038
+ const inputData = {
2039
+ agentName: params.agent.Name,
2040
+ subAgentName: subAgentRequest.name,
2041
+ message: subAgentRequest.message,
2042
+ terminateAfter: subAgentRequest.terminateAfter,
2043
+ conversationMessages: params.conversationMessages,
2044
+ parentAgentHierarchy: this._agentHierarchy,
2045
+ relationshipType: 'related'
2046
+ };
2047
+ const stepEntity = await this.createStepEntity('Sub-Agent', `Execute Related Sub-Agent: ${subAgentRequest.name}`, params.contextUser, subAgentEntity.ID, inputData, undefined, previousDecision.newPayload);
2048
+ this.incrementExecutionCount(subAgentEntity.ID);
2049
+ try {
2050
+ stepEntity.PayloadAtStart = JSON.stringify(previousDecision.newPayload);
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);
2075
+ let mergedPayload = previousDecision.newPayload;
2076
+ let currentStepPayloadChangeResult = undefined;
2077
+ if (subAgentResult.success && relationship.SubAgentOutputMapping) {
2078
+ const payloadChange = this.applySubAgentOutputMapping(subAgentResult.payload, previousDecision.newPayload, relationship.SubAgentOutputMapping);
2079
+ if (payloadChange && payloadChange.updateElements) {
2080
+ const mergeResult = this._payloadManager.applyAgentChangeRequest(previousDecision.newPayload, payloadChange, {
2081
+ validateChanges: true,
2082
+ logChanges: true,
2083
+ analyzeChanges: true,
2084
+ generateDiff: true,
2085
+ agentName: `${subAgentRequest.name} (related agent mapping)`,
2086
+ verbose: params.verbose === true || (0, core_1.IsVerboseLoggingEnabled)()
2087
+ });
2088
+ mergedPayload = mergeResult.result;
2089
+ currentStepPayloadChangeResult = this.buildPayloadChangeResultSummary(mergeResult);
2090
+ if (mergeResult.warnings.length > 0 && (params.verbose === true || (0, core_1.IsVerboseLoggingEnabled)())) {
2091
+ (0, core_1.LogStatus)(`Related sub-agent mapping warnings: ${mergeResult.warnings.join('; ')}`);
2092
+ }
2093
+ }
2094
+ }
2095
+ const shouldTerminate = subAgentRequest.terminateAfter === true;
2096
+ const outputData = {
2097
+ subAgentResult: {
2098
+ success: subAgentResult.success,
2099
+ finalStep: subAgentResult.agentRun?.FinalStep,
2100
+ errorMessage: subAgentResult.agentRun?.ErrorMessage,
2101
+ stepCount: subAgentResult.agentRun?.Steps?.length || 0,
2102
+ hasMergedPayload: !!(relationship.SubAgentOutputMapping && mergedPayload !== previousDecision.newPayload)
2103
+ },
2104
+ shouldTerminate: shouldTerminate,
2105
+ nextStep: shouldTerminate ? 'success' : 'retry',
2106
+ ...(currentStepPayloadChangeResult && {
2107
+ payloadChangeResult: currentStepPayloadChangeResult
2108
+ })
2109
+ };
2110
+ if (stepEntity) {
2111
+ stepEntity.PayloadAtEnd = JSON.stringify(mergedPayload);
2112
+ }
2113
+ await this.finalizeStepEntity(stepEntity, subAgentResult.success, subAgentResult.agentRun?.ErrorMessage, outputData);
2114
+ if (subAgentResult.agentRun?.FinalStep === 'Chat') {
2115
+ return {
2116
+ step: 'Chat',
2117
+ terminate: true,
2118
+ message: subAgentResult.agentRun?.Message || null,
2119
+ previousPayload: previousDecision?.newPayload,
2120
+ newPayload: previousDecision?.newPayload
2121
+ };
2122
+ }
2123
+ const subAgentSummary = {
2124
+ agentName: params.agent.Name,
2125
+ subAgentName: subAgentRequest.name,
2126
+ success: subAgentResult.success,
2127
+ payload: subAgentResult.payload,
2128
+ errorMessage: subAgentResult.agentRun?.ErrorMessage
2129
+ };
2130
+ params.conversationMessages.push({
2131
+ role: 'user',
2132
+ content: `Related sub-agent "${subAgentRequest.name}" completed:\n${JSON.stringify(subAgentSummary, null, 2)}`
2133
+ });
2134
+ if (this._agentRun) {
2135
+ this._agentRun.FinalPayloadObject = mergedPayload;
2136
+ }
2137
+ return {
2138
+ ...subAgentResult,
2139
+ step: subAgentResult.success ? 'Success' : 'Failed',
2140
+ terminate: shouldTerminate,
2141
+ previousPayload: previousDecision?.newPayload,
2142
+ newPayload: mergedPayload
2143
+ };
2144
+ }
2145
+ catch (error) {
2146
+ const payload = stepEntity.PayloadAtEnd ? JSON.parse(stepEntity.PayloadAtEnd) : previousDecision?.newPayload || params.payload;
2147
+ stepEntity.PayloadAtEnd = payload ? JSON.stringify(payload) : null;
2148
+ await this.finalizeStepEntity(stepEntity, false, error.message);
2149
+ return {
2150
+ errorMessage: `Related sub-agent execution failed: ${error.message}`,
2151
+ step: 'Failed',
2152
+ terminate: false,
2153
+ previousPayload: payload,
2154
+ newPayload: payload
2155
+ };
2156
+ }
2157
+ }
2158
+ applySubAgentOutputMapping(subAgentResult, _parentPayload, mappingConfig) {
2159
+ try {
2160
+ const mapping = JSON.parse(mappingConfig);
2161
+ const updateObj = {};
2162
+ for (const [subAgentPath, parentPath] of Object.entries(mapping)) {
2163
+ let value;
2164
+ if (subAgentPath === '*') {
2165
+ value = subAgentResult;
2166
+ }
2167
+ else {
2168
+ value = this.getValueFromPath(subAgentResult, subAgentPath);
2169
+ }
2170
+ if (value !== undefined) {
2171
+ const pathParts = parentPath.split('.');
2172
+ let current = updateObj;
2173
+ for (let i = 0; i < pathParts.length - 1; i++) {
2174
+ const part = pathParts[i];
2175
+ if (!(part in current)) {
2176
+ current[part] = {};
2177
+ }
2178
+ current = current[part];
2179
+ }
2180
+ current[pathParts[pathParts.length - 1]] = value;
2181
+ }
2182
+ }
2183
+ if (Object.keys(updateObj).length === 0) {
2184
+ return null;
2185
+ }
2186
+ return {
2187
+ updateElements: updateObj
2188
+ };
2189
+ }
2190
+ catch (error) {
2191
+ (0, core_1.LogError)(`Failed to parse SubAgentOutputMapping: ${error.message}`);
2192
+ return null;
2193
+ }
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
+ }
2261
+ getValueFromPath(obj, path) {
2262
+ const parts = path.split('.');
2263
+ let current = obj;
2264
+ for (const part of parts) {
2265
+ if (!part)
2266
+ continue;
2267
+ const arrayMatch = part.match(/^([^[]+)\[(\d+)\]$/);
2268
+ if (arrayMatch) {
2269
+ const arrayName = arrayMatch[1];
2270
+ const index = parseInt(arrayMatch[2], 10);
2271
+ if (current && typeof current === 'object' && arrayName in current) {
2272
+ current = current[arrayName];
2273
+ if (Array.isArray(current) && index >= 0 && index < current.length) {
2274
+ current = current[index];
2275
+ }
2276
+ else {
2277
+ return undefined;
2278
+ }
2279
+ }
2280
+ else {
2281
+ return undefined;
2282
+ }
2283
+ }
2284
+ else {
2285
+ if (current && typeof current === 'object' && part in current) {
2286
+ current = current[part];
2287
+ }
2288
+ else {
2289
+ return undefined;
2290
+ }
2291
+ }
2292
+ }
2293
+ return current;
2294
+ }
1774
2295
  async executeActionsStep(params, config, previousDecision) {
1775
2296
  try {
1776
2297
  const currentPayload = previousDecision?.newPayload || previousDecision?.previousPayload || params.payload;
@@ -1781,35 +2302,34 @@ class BaseAgent {
1781
2302
  let progressMessage;
1782
2303
  if (actions.length === 1) {
1783
2304
  const aa = actions[0];
1784
- progressMessage = `Executing action: **${aa.name}**`;
2305
+ progressMessage = `Executing **${aa.name}** action`;
1785
2306
  if (aa.params && Object.keys(aa.params).length > 0) {
1786
2307
  const paramsList = Object.entries(aa.params)
1787
2308
  .map(([key, value]) => {
1788
- const displayValue = typeof value === 'object'
1789
- ? JSON.stringify(value, null, 2)
1790
- : String(value);
1791
- return ` - \`${key}\`: ${displayValue}`;
2309
+ const displayValue = this.formatParamValueForMessage(value);
2310
+ return `• **${key}**: ${displayValue}`;
1792
2311
  })
1793
2312
  .join('\n');
1794
- progressMessage += `\n${paramsList}`;
2313
+ progressMessage += ` with parameters:\n${paramsList}`;
2314
+ }
2315
+ else {
2316
+ progressMessage += '.';
1795
2317
  }
1796
2318
  }
1797
2319
  else {
1798
- progressMessage = `Executing ${actions.length} actions:`;
1799
- actions.forEach(aa => {
1800
- 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}**`;
1801
2322
  if (aa.params && Object.keys(aa.params).length > 0) {
1802
2323
  const paramsList = Object.entries(aa.params)
1803
2324
  .map(([key, value]) => {
1804
- const displayValue = typeof value === 'object'
1805
- ? JSON.stringify(value, null, 2)
1806
- : String(value);
1807
- return ` - \`${key}\`: ${displayValue}`;
2325
+ const displayValue = this.formatParamValueForMessage(value);
2326
+ return ` • **${key}**: ${displayValue}`;
1808
2327
  })
1809
2328
  .join('\n');
1810
- progressMessage += `\n${paramsList}`;
2329
+ actionText += `\n${paramsList}`;
1811
2330
  }
1812
- });
2331
+ return actionText;
2332
+ }).join('\n\n');
1813
2333
  }
1814
2334
  params.onProgress?.({
1815
2335
  step: 'action_execution',
@@ -1821,9 +2341,38 @@ class BaseAgent {
1821
2341
  },
1822
2342
  displayMode: 'live'
1823
2343
  });
1824
- const actionMessage = actions.length === 1
1825
- ? `I'm executing the "${actions[0].name}" action...`
1826
- : `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
+ }
1827
2376
  params.conversationMessages.push({
1828
2377
  role: 'assistant',
1829
2378
  content: actionMessage
@@ -1892,9 +2441,44 @@ class BaseAgent {
1892
2441
  });
1893
2442
  const failedActions = actionSummaries.filter(a => !a.success);
1894
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
+ }
1895
2478
  params.conversationMessages.push({
1896
2479
  role: 'user',
1897
- content: resultsMessage
2480
+ content: resultsMessage,
2481
+ metadata: metadata
1898
2482
  });
1899
2483
  let finalPayload = currentPayload;
1900
2484
  try {
@@ -1960,6 +2544,7 @@ class BaseAgent {
1960
2544
  confidence: previousDecision.confidence,
1961
2545
  previousPayload: previousDecision.previousPayload,
1962
2546
  newPayload: previousDecision.newPayload || previousDecision.previousPayload,
2547
+ suggestedResponses: previousDecision.suggestedResponses
1963
2548
  };
1964
2549
  }
1965
2550
  async createFailureResult(errorMessage, contextUser) {
@@ -2029,7 +2614,8 @@ class BaseAgent {
2029
2614
  return {
2030
2615
  success: finalStep.step === 'Success' || finalStep.step === 'Chat',
2031
2616
  payload,
2032
- agentRun: this._agentRun
2617
+ agentRun: this._agentRun,
2618
+ suggestedResponses: finalStep.suggestedResponses
2033
2619
  };
2034
2620
  }
2035
2621
  calculateTokenStats() {
@@ -2090,6 +2676,236 @@ class BaseAgent {
2090
2676
  }
2091
2677
  return violations;
2092
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
+ }
2093
2909
  }
2094
2910
  exports.BaseAgent = BaseAgent;
2095
2911
  BaseAgent.MAX_VALIDATION_RETRIES = 10;