@memberjunction/ai-agents 5.20.0 → 5.22.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.
- package/README.md +13 -13
- package/dist/AgentRunner.d.ts +7 -5
- package/dist/AgentRunner.d.ts.map +1 -1
- package/dist/AgentRunner.js +22 -17
- package/dist/AgentRunner.js.map +1 -1
- package/dist/KnowledgeAgent.d.ts +100 -0
- package/dist/KnowledgeAgent.d.ts.map +1 -0
- package/dist/KnowledgeAgent.js +347 -0
- package/dist/KnowledgeAgent.js.map +1 -0
- package/dist/agent-types/loop-agent-prompt-params.d.ts +8 -0
- package/dist/agent-types/loop-agent-prompt-params.d.ts.map +1 -1
- package/dist/agent-types/loop-agent-prompt-params.js +1 -0
- package/dist/agent-types/loop-agent-prompt-params.js.map +1 -1
- package/dist/base-agent.d.ts +51 -3
- package/dist/base-agent.d.ts.map +1 -1
- package/dist/base-agent.js +218 -72
- package/dist/base-agent.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +13 -12
package/dist/base-agent.js
CHANGED
|
@@ -81,6 +81,13 @@ export class BaseAgent {
|
|
|
81
81
|
* @private
|
|
82
82
|
*/
|
|
83
83
|
this._metadata = new Metadata();
|
|
84
|
+
/**
|
|
85
|
+
* Active per-request metadata provider, set at the start of Execute().
|
|
86
|
+
* Defaults to the global Metadata.Provider; overridden when a per-request
|
|
87
|
+
* provider is passed through ExecuteAgentParams.provider for server isolation.
|
|
88
|
+
* @private
|
|
89
|
+
*/
|
|
90
|
+
this._activeProvider = Metadata.Provider;
|
|
84
91
|
/**
|
|
85
92
|
* This is state information that is specific to the agent type. BaseAgent doesn't know what
|
|
86
93
|
* this contains or care, it is just responsible for keeping this, giving the Agent Type the
|
|
@@ -173,6 +180,17 @@ export class BaseAgent {
|
|
|
173
180
|
* @since 2.123.0
|
|
174
181
|
*/
|
|
175
182
|
this._effectiveActions = [];
|
|
183
|
+
/**
|
|
184
|
+
* Counts only prompt (LLM) executions, NOT all agent steps.
|
|
185
|
+
* Used for message expiration age calculations so that `expirationTurns`
|
|
186
|
+
* semantically means "number of LLM calls" rather than "number of steps"
|
|
187
|
+
* (which includes actions, loops, sub-agents, etc.).
|
|
188
|
+
*
|
|
189
|
+
* Without this, a ForEach loop over 100 items would bump the step counter
|
|
190
|
+
* by 100, causing messages to expire far too early even though only one
|
|
191
|
+
* prompt execution occurred after them.
|
|
192
|
+
*/
|
|
193
|
+
this._promptTurnCount = 0;
|
|
176
194
|
/**
|
|
177
195
|
* Execution limits for dynamically added actions.
|
|
178
196
|
* Maps action IDs to their MaxExecutionsPerRun limit.
|
|
@@ -808,6 +826,9 @@ export class BaseAgent {
|
|
|
808
826
|
* ```
|
|
809
827
|
*/
|
|
810
828
|
async Execute(params) {
|
|
829
|
+
// Capture per-request provider for the duration of this execution so all entity
|
|
830
|
+
// saves go through the isolated provider, never the global singleton's transaction.
|
|
831
|
+
this._activeProvider = params.provider || Metadata.Provider;
|
|
811
832
|
try {
|
|
812
833
|
this.logStatus(`🤖 Starting execution of agent '${params.agent.Name}'`, true, params);
|
|
813
834
|
// Check permissions - user must have run permission or be the owner
|
|
@@ -1003,8 +1024,10 @@ export class BaseAgent {
|
|
|
1003
1024
|
if (params.cancellationToken?.aborted) {
|
|
1004
1025
|
throw new Error('Cancelled during execution');
|
|
1005
1026
|
}
|
|
1006
|
-
// Prune and compact expired messages before executing the next step
|
|
1007
|
-
|
|
1027
|
+
// Prune and compact expired messages before executing the next step.
|
|
1028
|
+
// Uses _promptTurnCount (not stepCount) so expiration is measured in prompt
|
|
1029
|
+
// executions, not all steps (which include actions, loops, sub-agents, etc.)
|
|
1030
|
+
await this.pruneAndCompactExpiredMessages(params, this._promptTurnCount);
|
|
1008
1031
|
// Execute the current step based on previous decision or initial prompt
|
|
1009
1032
|
this.logStatus(`🔄 Executing step ${stepCount + 1} for agent '${params.agent.Name}'`, true, params);
|
|
1010
1033
|
const nextStep = await this.executeNextStep(params, config, currentNextStep, stepCount);
|
|
@@ -1584,6 +1607,8 @@ export class BaseAgent {
|
|
|
1584
1607
|
promptParams.apiKeys = params.apiKeys;
|
|
1585
1608
|
this.logStatus(`🔑 Using ${params.apiKeys.length} API key(s) provided at runtime`, true, params);
|
|
1586
1609
|
}
|
|
1610
|
+
// Thread the per-request provider so prompt run records are saved through the isolated provider
|
|
1611
|
+
promptParams.provider = params.provider || this._activeProvider;
|
|
1587
1612
|
return promptParams;
|
|
1588
1613
|
}
|
|
1589
1614
|
/**
|
|
@@ -2815,13 +2840,13 @@ export class BaseAgent {
|
|
|
2815
2840
|
newPayload: payload
|
|
2816
2841
|
};
|
|
2817
2842
|
}
|
|
2818
|
-
//
|
|
2819
|
-
const
|
|
2843
|
+
// Use prompt turn count for age calculations (not step count)
|
|
2844
|
+
const currentPromptTurn = this._promptTurnCount;
|
|
2820
2845
|
// Try multiple recovery strategies in order
|
|
2821
2846
|
const strategies = [
|
|
2822
|
-
() => this.recoveryStrategy_RemoveOldestActionResults(params, tokensToSave,
|
|
2823
|
-
() => this.recoveryStrategy_CompactOldActionResults(params, tokensToSave,
|
|
2824
|
-
() => this.recoveryStrategy_RemoveOldestActionResults(params, tokensToSave,
|
|
2847
|
+
() => this.recoveryStrategy_RemoveOldestActionResults(params, tokensToSave, currentPromptTurn, 5),
|
|
2848
|
+
() => this.recoveryStrategy_CompactOldActionResults(params, tokensToSave, currentPromptTurn, 3),
|
|
2849
|
+
() => this.recoveryStrategy_RemoveOldestActionResults(params, tokensToSave, currentPromptTurn, 2),
|
|
2825
2850
|
() => this.recoveryStrategy_CompactAllActionResults(params, tokensToSave),
|
|
2826
2851
|
() => Promise.resolve(this.recoveryStrategy_TrimLastUserMessage(params, tokensToSave))
|
|
2827
2852
|
];
|
|
@@ -3314,8 +3339,8 @@ The context is now within limits. Please retry your request with the recovered c
|
|
|
3314
3339
|
async ExecuteSubAgent(params, subAgentRequest, subAgent, stepEntity, payload, contextMessage, stepCount = 0) {
|
|
3315
3340
|
try {
|
|
3316
3341
|
this.logStatus(`🤖 Executing sub-agent '${subAgentRequest.name}'`, true, params);
|
|
3317
|
-
// Create a new AgentRunner instance
|
|
3318
|
-
const runner = new AgentRunner();
|
|
3342
|
+
// Create a new AgentRunner instance with the same isolated provider
|
|
3343
|
+
const runner = new AgentRunner(params.provider || this._activeProvider);
|
|
3319
3344
|
// Prepare messages for sub-agent using database-configured message mode
|
|
3320
3345
|
const subAgentMessages = this.prepareSubAgentMessages(params, subAgentRequest, subAgent, contextMessage);
|
|
3321
3346
|
// Set parent run ID in the sub-agent's execution
|
|
@@ -3561,18 +3586,15 @@ The context is now within limits. Please retry your request with the recovered c
|
|
|
3561
3586
|
if (!result.success && result.agentRun?.ErrorMessage) {
|
|
3562
3587
|
lines.push(`**Error:** ${result.agentRun.ErrorMessage}`);
|
|
3563
3588
|
}
|
|
3564
|
-
// Payload — the actual result data the parent agent cares about
|
|
3589
|
+
// Payload — the actual result data the parent agent cares about.
|
|
3590
|
+
// Not truncated by default — the expiration/compaction lifecycle handles
|
|
3591
|
+
// context window management. Truncating here permanently discards data
|
|
3592
|
+
// before the LLM ever sees it.
|
|
3565
3593
|
if (result.payload != null) {
|
|
3566
3594
|
const payloadStr = typeof result.payload === 'string'
|
|
3567
3595
|
? result.payload
|
|
3568
3596
|
: JSON.stringify(result.payload);
|
|
3569
|
-
|
|
3570
|
-
if (payloadStr.length > 4000) {
|
|
3571
|
-
lines.push(`**Payload** (truncated):\n${payloadStr.substring(0, 4000)}…`);
|
|
3572
|
-
}
|
|
3573
|
-
else {
|
|
3574
|
-
lines.push(`**Payload:**\n${payloadStr}`);
|
|
3575
|
-
}
|
|
3597
|
+
lines.push(`**Payload:**\n${payloadStr}`);
|
|
3576
3598
|
}
|
|
3577
3599
|
return lines.join('\n');
|
|
3578
3600
|
}
|
|
@@ -3580,14 +3602,19 @@ The context is now within limits. Please retry your request with the recovered c
|
|
|
3580
3602
|
* Formats a single output parameter value for inclusion in action result
|
|
3581
3603
|
* markdown. Scalars are shown inline with backtick formatting; objects and
|
|
3582
3604
|
* arrays use compact (single-line) JSON to avoid the indentation overhead
|
|
3583
|
-
* of pretty-printed JSON.
|
|
3605
|
+
* of pretty-printed JSON.
|
|
3606
|
+
*
|
|
3607
|
+
* By default, values are **not truncated** — the expiration/compaction lifecycle
|
|
3608
|
+
* handles context window management. Truncating at formatting time permanently
|
|
3609
|
+
* discards data before the LLM ever sees it. Pass an explicit maxLength only
|
|
3610
|
+
* when you have a specific reason to limit output size.
|
|
3584
3611
|
*
|
|
3585
3612
|
* @param value - The parameter value (any type)
|
|
3586
|
-
* @param maxLength - Maximum character length before truncation (default
|
|
3613
|
+
* @param maxLength - Maximum character length before truncation (0 = no limit, default 0)
|
|
3587
3614
|
* @returns Formatted string
|
|
3588
3615
|
* @private
|
|
3589
3616
|
*/
|
|
3590
|
-
formatParamValueForResult(value, maxLength =
|
|
3617
|
+
formatParamValueForResult(value, maxLength = 0) {
|
|
3591
3618
|
if (value === null || value === undefined) {
|
|
3592
3619
|
return '`null`';
|
|
3593
3620
|
}
|
|
@@ -3602,7 +3629,7 @@ The context is now within limits. Please retry your request with the recovered c
|
|
|
3602
3629
|
// Compact JSON (no pretty-printing) for objects/arrays
|
|
3603
3630
|
stringValue = JSON.stringify(value);
|
|
3604
3631
|
}
|
|
3605
|
-
if (stringValue.length > maxLength) {
|
|
3632
|
+
if (maxLength > 0 && stringValue.length > maxLength) {
|
|
3606
3633
|
return `${stringValue.substring(0, maxLength)}…`;
|
|
3607
3634
|
}
|
|
3608
3635
|
return stringValue;
|
|
@@ -3816,8 +3843,10 @@ The context is now within limits. Please retry your request with the recovered c
|
|
|
3816
3843
|
}
|
|
3817
3844
|
}
|
|
3818
3845
|
}
|
|
3846
|
+
// Reset prompt turn counter for this execution
|
|
3847
|
+
this._promptTurnCount = 0;
|
|
3819
3848
|
// Create MJAIAgentRunEntity
|
|
3820
|
-
this._agentRun = await this.
|
|
3849
|
+
this._agentRun = await (params.provider || this._activeProvider).GetEntityObject('MJ: AI Agent Runs', params.contextUser);
|
|
3821
3850
|
this._agentRun.AgentID = params.agent.ID;
|
|
3822
3851
|
if (params.conversationDetailId) {
|
|
3823
3852
|
this._agentRun.ConversationDetailID = params.conversationDetailId;
|
|
@@ -3970,7 +3999,7 @@ The context is now within limits. Please retry your request with the recovered c
|
|
|
3970
3999
|
* @returns {Promise<MJAIAgentRunStepEntityExtended>} - The created step entity
|
|
3971
4000
|
*/
|
|
3972
4001
|
async createStepEntity(params) {
|
|
3973
|
-
const stepEntity = await this.
|
|
4002
|
+
const stepEntity = await this._activeProvider.GetEntityObject('MJ: AI Agent Run Steps', params.contextUser);
|
|
3974
4003
|
stepEntity.AgentRunID = this._agentRun.ID;
|
|
3975
4004
|
// Step number is based on current count of steps + 1
|
|
3976
4005
|
stepEntity.StepNumber = (this._agentRun.Steps?.length || 0) + 1;
|
|
@@ -4211,8 +4240,7 @@ The context is now within limits. Please retry your request with the recovered c
|
|
|
4211
4240
|
// Check if this is a message expansion request
|
|
4212
4241
|
if (previousDecision.messageIndex !== undefined) {
|
|
4213
4242
|
// Handle message expansion before retrying
|
|
4214
|
-
|
|
4215
|
-
this.executeExpandMessageStep(previousDecision, params, currentStepCount);
|
|
4243
|
+
this.executeExpandMessageStep(previousDecision, params, this._promptTurnCount);
|
|
4216
4244
|
}
|
|
4217
4245
|
return await this.executePromptStep(params, config, previousDecision, stepCount);
|
|
4218
4246
|
case 'Sub-Agent':
|
|
@@ -4420,12 +4448,11 @@ The context is now within limits. Please retry your request with the recovered c
|
|
|
4420
4448
|
};
|
|
4421
4449
|
// Execute the prompt
|
|
4422
4450
|
const promptResult = await this.executePrompt(promptParams);
|
|
4423
|
-
//
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
});
|
|
4451
|
+
// Increment prompt-specific turn counter (used for expiration age calculations)
|
|
4452
|
+
this._promptTurnCount++;
|
|
4453
|
+
// Loop and sub-agent results now use the standard expiration/compaction lifecycle
|
|
4454
|
+
// (messageType: 'loop-result' / 'sub-agent-result') instead of being deleted after
|
|
4455
|
+
// one prompt turn. The processMessageExpiration() method handles their lifecycle.
|
|
4429
4456
|
// Update step entity with AIPromptRun ID if available
|
|
4430
4457
|
if (promptResult.promptRun?.ID) {
|
|
4431
4458
|
stepEntity.TargetLogID = promptResult.promptRun.ID;
|
|
@@ -4940,31 +4967,33 @@ The context is now within limits. Please retry your request with the recovered c
|
|
|
4940
4967
|
// This ensures parent agents can override child Chat steps
|
|
4941
4968
|
return await this.validateChatNextStep(params, chatStep, mergedPayload, this._agentRun, stepEntity);
|
|
4942
4969
|
}
|
|
4943
|
-
//
|
|
4944
|
-
|
|
4945
|
-
|
|
4970
|
+
// Add user message with the sub-agent results using markdown format
|
|
4971
|
+
// and expiration metadata so it persists across multiple prompt turns
|
|
4972
|
+
const resultMessage = this.formatSubAgentResultAsMarkdown(subAgentRequest.name, subAgentResult);
|
|
4973
|
+
const subAgentMetadata = {
|
|
4974
|
+
turnAdded: this._promptTurnCount,
|
|
4975
|
+
messageType: 'sub-agent-result',
|
|
4946
4976
|
subAgentName: subAgentRequest.name,
|
|
4947
4977
|
subAgentId: subAgentEntity.ID,
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
// do NOT include payload here as this goes to the LLM and
|
|
4952
|
-
// we don't need that there, too many tokens and LLM already gets
|
|
4953
|
-
// payload the normal way
|
|
4978
|
+
// Default: keep sub-agent results for 3 turns then remove
|
|
4979
|
+
expirationTurns: 3,
|
|
4980
|
+
expirationMode: 'Remove'
|
|
4954
4981
|
};
|
|
4955
|
-
//
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4982
|
+
// Apply global override if configured
|
|
4983
|
+
if (params.messageExpirationOverride) {
|
|
4984
|
+
const override = params.messageExpirationOverride;
|
|
4985
|
+
if (override.expirationTurns != null) {
|
|
4986
|
+
subAgentMetadata.expirationTurns = override.expirationTurns;
|
|
4987
|
+
subAgentMetadata.expirationMode = override.expirationMode || 'Remove';
|
|
4988
|
+
subAgentMetadata.compactMode = override.compactMode;
|
|
4989
|
+
subAgentMetadata.compactLength = override.compactLength;
|
|
4990
|
+
subAgentMetadata.compactPromptId = override.compactPromptId;
|
|
4991
|
+
}
|
|
4992
|
+
}
|
|
4959
4993
|
params.conversationMessages.push({
|
|
4960
4994
|
role: 'user',
|
|
4961
4995
|
content: resultMessage,
|
|
4962
|
-
metadata:
|
|
4963
|
-
_temporary: true,
|
|
4964
|
-
_subAgentResult: true,
|
|
4965
|
-
subAgentName: subAgentRequest.name,
|
|
4966
|
-
subAgentId: subAgentEntity.ID
|
|
4967
|
-
}
|
|
4996
|
+
metadata: subAgentMetadata
|
|
4968
4997
|
});
|
|
4969
4998
|
// Set PayloadAtEnd with the merged payload
|
|
4970
4999
|
if (stepEntity) {
|
|
@@ -5217,17 +5246,31 @@ The context is now within limits. Please retry your request with the recovered c
|
|
|
5217
5246
|
// This ensures parent agents can override child Chat steps
|
|
5218
5247
|
return await this.validateChatNextStep(params, chatStep, mergedPayload, this._agentRun, stepEntity);
|
|
5219
5248
|
}
|
|
5220
|
-
// Add sub-agent result to conversation as user message
|
|
5221
|
-
|
|
5222
|
-
|
|
5249
|
+
// Add sub-agent result to conversation as user message using markdown format
|
|
5250
|
+
// and expiration metadata so it persists across multiple prompt turns
|
|
5251
|
+
const relatedResultMessage = this.formatSubAgentResultAsMarkdown(subAgentRequest.name, subAgentResult);
|
|
5252
|
+
const relatedMetadata = {
|
|
5253
|
+
turnAdded: this._promptTurnCount,
|
|
5254
|
+
messageType: 'sub-agent-result',
|
|
5223
5255
|
subAgentName: subAgentRequest.name,
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5256
|
+
subAgentId: subAgentEntity.ID,
|
|
5257
|
+
expirationTurns: 3,
|
|
5258
|
+
expirationMode: 'Remove'
|
|
5227
5259
|
};
|
|
5260
|
+
if (params.messageExpirationOverride) {
|
|
5261
|
+
const override = params.messageExpirationOverride;
|
|
5262
|
+
if (override.expirationTurns != null) {
|
|
5263
|
+
relatedMetadata.expirationTurns = override.expirationTurns;
|
|
5264
|
+
relatedMetadata.expirationMode = override.expirationMode || 'Remove';
|
|
5265
|
+
relatedMetadata.compactMode = override.compactMode;
|
|
5266
|
+
relatedMetadata.compactLength = override.compactLength;
|
|
5267
|
+
relatedMetadata.compactPromptId = override.compactPromptId;
|
|
5268
|
+
}
|
|
5269
|
+
}
|
|
5228
5270
|
params.conversationMessages.push({
|
|
5229
5271
|
role: 'user',
|
|
5230
|
-
content:
|
|
5272
|
+
content: relatedResultMessage,
|
|
5273
|
+
metadata: relatedMetadata
|
|
5231
5274
|
});
|
|
5232
5275
|
// Update the agent run's current payload
|
|
5233
5276
|
if (this._agentRun) {
|
|
@@ -5676,9 +5719,8 @@ The context is now within limits. Please retry your request with the recovered c
|
|
|
5676
5719
|
}
|
|
5677
5720
|
// Only add metadata if we have expiration settings
|
|
5678
5721
|
if (minExpirationTurns !== null && expirationMode !== 'None') {
|
|
5679
|
-
const currentStepCount = this._agentRun?.Steps?.length || 0;
|
|
5680
5722
|
metadata = {
|
|
5681
|
-
turnAdded:
|
|
5723
|
+
turnAdded: this._promptTurnCount,
|
|
5682
5724
|
messageType: 'action-result',
|
|
5683
5725
|
expirationTurns: minExpirationTurns,
|
|
5684
5726
|
expirationMode: expirationMode,
|
|
@@ -5820,7 +5862,7 @@ The context is now within limits. Please retry your request with the recovered c
|
|
|
5820
5862
|
const requestForUserId = this.resolveUserFromStrategy(resolvedStrategy, params);
|
|
5821
5863
|
const priority = resolvedStrategy?.priority ?? 50;
|
|
5822
5864
|
const expirationMinutes = resolvedStrategy?.expirationMinutes;
|
|
5823
|
-
const request = await this.
|
|
5865
|
+
const request = await (params.provider || this._activeProvider).GetEntityObject('MJ: AI Agent Requests', params.contextUser);
|
|
5824
5866
|
request.NewRecord();
|
|
5825
5867
|
request.AgentID = params.agent.ID;
|
|
5826
5868
|
request.RequestedAt = new Date();
|
|
@@ -6297,6 +6339,8 @@ The context is now within limits. Please retry your request with the recovered c
|
|
|
6297
6339
|
else if (forEach.subAgent) {
|
|
6298
6340
|
const subAgentStep = { step: 'Sub-Agent', subAgent: forEach.subAgent, newPayload: currentPayload, previousPayload: currentPayload };
|
|
6299
6341
|
result = await this.processSubAgentStep(params, subAgentStep, parentStepId, item);
|
|
6342
|
+
// Attach formatted summary as priorStepResult so formatLoopResultsAsMarkdown can render it
|
|
6343
|
+
result.priorStepResult = this.formatSubAgentResultAsMarkdown(forEach.subAgent.name, result);
|
|
6300
6344
|
}
|
|
6301
6345
|
else {
|
|
6302
6346
|
throw new Error('ForEach missing action/subAgent');
|
|
@@ -6318,7 +6362,7 @@ The context is now within limits. Please retry your request with the recovered c
|
|
|
6318
6362
|
loopStepEntity.PayloadAtEnd = this.serializePayloadAtEnd(loopResults.finalPayload);
|
|
6319
6363
|
await this.finalizeStepEntity(loopStepEntity, loopResults.errors.length === 0, loopResults.errors.join('\n\n'), loopResults);
|
|
6320
6364
|
if (this.AgentTypeInstance.InjectLoopResultsAsMessage) {
|
|
6321
|
-
this.injectLoopResultsMessage('ForEach', forEach.collectionPath, loopResults.results, loopResults.errors, params);
|
|
6365
|
+
this.injectLoopResultsMessage('ForEach', forEach.collectionPath, loopResults.results, loopResults.errors, params, forEach.action?.name);
|
|
6322
6366
|
}
|
|
6323
6367
|
return {
|
|
6324
6368
|
step: 'Retry',
|
|
@@ -6329,20 +6373,121 @@ The context is now within limits. Please retry your request with the recovered c
|
|
|
6329
6373
|
};
|
|
6330
6374
|
}
|
|
6331
6375
|
/**
|
|
6332
|
-
* Helper: Inject loop results
|
|
6376
|
+
* Helper: Inject loop results into conversation with expiration/compaction metadata.
|
|
6377
|
+
* Uses the same lifecycle as non-loop action results so results persist for N turns
|
|
6378
|
+
* rather than being deleted after a single prompt turn.
|
|
6333
6379
|
*/
|
|
6334
|
-
injectLoopResultsMessage(loopType, collectionOrCondition, results, errors, params) {
|
|
6335
|
-
// grab the priorStepResult from within each result item and put that into a new array
|
|
6336
|
-
const extractedResults = results.map(r => r.priorStepResult);
|
|
6380
|
+
injectLoopResultsMessage(loopType, collectionOrCondition, results, errors, params, actionName) {
|
|
6337
6381
|
const label = loopType === 'ForEach' ? 'Collection' : 'Condition';
|
|
6382
|
+
const content = `## Loop Completed\n**Type:** ${loopType}\n**${label}:** ${collectionOrCondition}\n` +
|
|
6383
|
+
`**Processed:** ${results.length}, **Errors:** ${errors.length}\n\n` +
|
|
6384
|
+
this.formatLoopResultsAsMarkdown(results, errors);
|
|
6385
|
+
// Resolve expiration metadata from the loop's action config (most-restrictive-wins)
|
|
6386
|
+
const metadata = this.resolveLoopExpirationMetadata(params, actionName);
|
|
6338
6387
|
params.conversationMessages.push({
|
|
6339
6388
|
role: 'user',
|
|
6340
|
-
content
|
|
6341
|
-
|
|
6342
|
-
`**Results:**\n\`\`\`json\n${JSON.stringify(extractedResults, null, 2)}\n\`\`\``,
|
|
6343
|
-
metadata: { _temporary: true, _loopResults: true }
|
|
6389
|
+
content,
|
|
6390
|
+
metadata
|
|
6344
6391
|
});
|
|
6345
6392
|
}
|
|
6393
|
+
/**
|
|
6394
|
+
* Formats loop iteration results as markdown. Handles two distinct result shapes
|
|
6395
|
+
* depending on whether the loop body executed actions or sub-agents:
|
|
6396
|
+
*
|
|
6397
|
+
* - **Action iterations**: `priorStepResult` is an `ActionResultSummary[]`, set by
|
|
6398
|
+
* `executeActionsStep()` (line ~7003). These are formatted via `formatActionResultsAsMarkdown()`.
|
|
6399
|
+
*
|
|
6400
|
+
* - **Sub-agent iterations**: `priorStepResult` is a **pre-formatted markdown string**,
|
|
6401
|
+
* set by the loop handlers (`executeSingleForEachIteration` / `executeSingleWhileIteration`)
|
|
6402
|
+
* which call `formatSubAgentResultAsMarkdown()` and assign the result to `priorStepResult`.
|
|
6403
|
+
* Since the markdown is already rendered upstream, this method simply includes it as-is.
|
|
6404
|
+
*
|
|
6405
|
+
* This two-path design means sub-agent formatting happens at assignment time (in the loop
|
|
6406
|
+
* handler), while action formatting happens at render time (here). Both produce the same
|
|
6407
|
+
* markdown style used by non-loop results, ensuring consistency across the codebase.
|
|
6408
|
+
*/
|
|
6409
|
+
formatLoopResultsAsMarkdown(results, errors) {
|
|
6410
|
+
const lines = [];
|
|
6411
|
+
for (let i = 0; i < results.length; i++) {
|
|
6412
|
+
const iterResult = results[i].priorStepResult;
|
|
6413
|
+
// Action iterations: priorStepResult is ActionResultSummary[] — format via shared helper
|
|
6414
|
+
if (Array.isArray(iterResult)) {
|
|
6415
|
+
lines.push(`### Iteration ${i + 1}`);
|
|
6416
|
+
lines.push(this.formatActionResultsAsMarkdown(iterResult));
|
|
6417
|
+
}
|
|
6418
|
+
else if (iterResult != null) {
|
|
6419
|
+
// Sub-agent iterations: priorStepResult is already a markdown string from
|
|
6420
|
+
// formatSubAgentResultAsMarkdown() called in the loop handler. Non-string
|
|
6421
|
+
// results (edge cases) are JSON-stringified as a fallback.
|
|
6422
|
+
lines.push(`### Iteration ${i + 1}`);
|
|
6423
|
+
const text = typeof iterResult === 'string'
|
|
6424
|
+
? iterResult
|
|
6425
|
+
: JSON.stringify(iterResult);
|
|
6426
|
+
lines.push(text);
|
|
6427
|
+
}
|
|
6428
|
+
// null/undefined priorStepResult: iteration produced no output — skip silently
|
|
6429
|
+
}
|
|
6430
|
+
if (errors.length > 0) {
|
|
6431
|
+
lines.push(`### Errors`);
|
|
6432
|
+
for (const err of errors) {
|
|
6433
|
+
const errMsg = typeof err === 'string' ? err : err?.message || JSON.stringify(err);
|
|
6434
|
+
lines.push(`• ✗ ${errMsg}`);
|
|
6435
|
+
}
|
|
6436
|
+
}
|
|
6437
|
+
return lines.join('\n');
|
|
6438
|
+
}
|
|
6439
|
+
/**
|
|
6440
|
+
* Resolves expiration/compaction metadata for loop result messages by looking up
|
|
6441
|
+
* the loop's action config. Uses the same most-restrictive-wins logic as
|
|
6442
|
+
* non-loop action result metadata resolution.
|
|
6443
|
+
*/
|
|
6444
|
+
resolveLoopExpirationMetadata(params, actionName) {
|
|
6445
|
+
const baseMetadata = {
|
|
6446
|
+
turnAdded: this._promptTurnCount,
|
|
6447
|
+
messageType: 'loop-result'
|
|
6448
|
+
};
|
|
6449
|
+
// Check for global override first
|
|
6450
|
+
if (params.messageExpirationOverride) {
|
|
6451
|
+
const override = params.messageExpirationOverride;
|
|
6452
|
+
if (override.expirationTurns != null && override.expirationMode !== 'None') {
|
|
6453
|
+
return {
|
|
6454
|
+
...baseMetadata,
|
|
6455
|
+
expirationTurns: override.expirationTurns,
|
|
6456
|
+
expirationMode: override.expirationMode || 'Remove',
|
|
6457
|
+
compactMode: override.compactMode,
|
|
6458
|
+
compactLength: override.compactLength,
|
|
6459
|
+
compactPromptId: override.compactPromptId
|
|
6460
|
+
};
|
|
6461
|
+
}
|
|
6462
|
+
}
|
|
6463
|
+
// Look up the loop's action config for expiration settings
|
|
6464
|
+
if (actionName) {
|
|
6465
|
+
const agentActions = AIEngine.Instance.AgentActions.filter(aa => UUIDsEqual(aa.AgentID, params.agent.ID));
|
|
6466
|
+
const effectiveActions = this._effectiveActions.length > 0
|
|
6467
|
+
? this._effectiveActions
|
|
6468
|
+
: ActionEngineServer.Instance.Actions.filter(a => agentActions.some(aa => UUIDsEqual(aa.ActionID, a.ID)));
|
|
6469
|
+
const matchedAction = effectiveActions.find(a => a.Name.trim().toLowerCase() === actionName.trim().toLowerCase());
|
|
6470
|
+
if (matchedAction) {
|
|
6471
|
+
const agentAction = agentActions.find(aa => UUIDsEqual(aa.ActionID, matchedAction.ID));
|
|
6472
|
+
if (agentAction?.ResultExpirationTurns != null && agentAction.ResultExpirationMode !== 'None') {
|
|
6473
|
+
return {
|
|
6474
|
+
...baseMetadata,
|
|
6475
|
+
expirationTurns: agentAction.ResultExpirationTurns,
|
|
6476
|
+
expirationMode: agentAction.ResultExpirationMode,
|
|
6477
|
+
compactMode: agentAction.CompactMode,
|
|
6478
|
+
compactLength: agentAction.CompactLength ?? undefined,
|
|
6479
|
+
compactPromptId: agentAction.CompactPromptID ?? undefined
|
|
6480
|
+
};
|
|
6481
|
+
}
|
|
6482
|
+
}
|
|
6483
|
+
}
|
|
6484
|
+
// Default: keep for 3 turns then remove (safe fallback so results don't vanish after 1 turn)
|
|
6485
|
+
return {
|
|
6486
|
+
...baseMetadata,
|
|
6487
|
+
expirationTurns: 3,
|
|
6488
|
+
expirationMode: 'Remove'
|
|
6489
|
+
};
|
|
6490
|
+
}
|
|
6346
6491
|
/**
|
|
6347
6492
|
* Helper: Find action with fuzzy matching for loops.
|
|
6348
6493
|
* Uses effective actions which includes both database-configured and dynamically added actions.
|
|
@@ -6482,6 +6627,8 @@ The context is now within limits. Please retry your request with the recovered c
|
|
|
6482
6627
|
else if (whileOp.subAgent) {
|
|
6483
6628
|
const subAgentStep = { step: 'Sub-Agent', subAgent: whileOp.subAgent, newPayload: currentPayload, previousPayload: currentPayload };
|
|
6484
6629
|
result = await this.processSubAgentStep(params, subAgentStep, parentStepId, attemptContext);
|
|
6630
|
+
// Attach formatted summary as priorStepResult so formatLoopResultsAsMarkdown can render it
|
|
6631
|
+
result.priorStepResult = this.formatSubAgentResultAsMarkdown(whileOp.subAgent.name, result);
|
|
6485
6632
|
}
|
|
6486
6633
|
else {
|
|
6487
6634
|
throw new Error('While missing action/subAgent');
|
|
@@ -6502,7 +6649,7 @@ The context is now within limits. Please retry your request with the recovered c
|
|
|
6502
6649
|
loopStepEntity.PayloadAtEnd = this.serializePayloadAtEnd(loopResults.finalPayload);
|
|
6503
6650
|
await this.finalizeStepEntity(loopStepEntity, loopResults.errors.length === 0, loopResults.errors.join('\n\n'), loopResults);
|
|
6504
6651
|
if (this.AgentTypeInstance.InjectLoopResultsAsMessage) {
|
|
6505
|
-
this.injectLoopResultsMessage('While', whileOp.condition, loopResults.results, loopResults.errors, params);
|
|
6652
|
+
this.injectLoopResultsMessage('While', whileOp.condition, loopResults.results, loopResults.errors, params, whileOp.action?.name);
|
|
6506
6653
|
}
|
|
6507
6654
|
return {
|
|
6508
6655
|
step: 'Retry',
|
|
@@ -6881,8 +7028,7 @@ The context is now within limits. Please retry your request with the recovered c
|
|
|
6881
7028
|
if (!this._agentRun) {
|
|
6882
7029
|
throw new Error('Cannot create compaction step: agent run not initialized');
|
|
6883
7030
|
}
|
|
6884
|
-
const
|
|
6885
|
-
const step = await md.GetEntityObject('MJ: AI Agent Run Steps', params.contextUser);
|
|
7031
|
+
const step = await (params.provider || this._activeProvider).GetEntityObject('MJ: AI Agent Run Steps', params.contextUser);
|
|
6886
7032
|
step.NewRecord();
|
|
6887
7033
|
step.AgentRunID = this._agentRun.ID;
|
|
6888
7034
|
step.StepType = 'Prompt';
|