@memberjunction/ai-agents 5.17.0 → 5.19.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/dist/ScratchpadManager.d.ts +109 -0
- package/dist/ScratchpadManager.d.ts.map +1 -0
- package/dist/ScratchpadManager.js +237 -0
- package/dist/ScratchpadManager.js.map +1 -0
- package/dist/agent-types/loop-agent-prompt-params.d.ts +19 -0
- package/dist/agent-types/loop-agent-prompt-params.d.ts.map +1 -1
- package/dist/agent-types/loop-agent-prompt-params.js +4 -1
- package/dist/agent-types/loop-agent-prompt-params.js.map +1 -1
- package/dist/agent-types/loop-agent-response-type.d.ts +8 -1
- package/dist/agent-types/loop-agent-response-type.d.ts.map +1 -1
- package/dist/agent-types/loop-agent-response-type.js.map +1 -1
- package/dist/agent-types/loop-agent-type.d.ts.map +1 -1
- package/dist/agent-types/loop-agent-type.js +3 -0
- package/dist/agent-types/loop-agent-type.js.map +1 -1
- package/dist/base-agent.d.ts +79 -7
- package/dist/base-agent.d.ts.map +1 -1
- package/dist/base-agent.js +257 -65
- 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 +12 -12
package/dist/base-agent.js
CHANGED
|
@@ -22,6 +22,7 @@ import { RerankerService } from '@memberjunction/ai-reranker';
|
|
|
22
22
|
import { AIPromptParams, ChildPromptParam, ConversationUtility, parseAssignmentStrategy } from '@memberjunction/ai-core-plus';
|
|
23
23
|
import { AgentRunner } from './AgentRunner.js';
|
|
24
24
|
import { PayloadManager } from './PayloadManager.js';
|
|
25
|
+
import { ScratchpadManager } from './ScratchpadManager.js';
|
|
25
26
|
import { AgentDataPreloader } from './AgentDataPreloader.js';
|
|
26
27
|
import { ConversationMessageResolver } from './utils/ConversationMessageResolver.js';
|
|
27
28
|
import _ from 'lodash';
|
|
@@ -158,6 +159,13 @@ export class BaseAgent {
|
|
|
158
159
|
* @private
|
|
159
160
|
*/
|
|
160
161
|
this._payloadManager = new PayloadManager();
|
|
162
|
+
/**
|
|
163
|
+
* Scratchpad manager for private agent working memory (notes + task list).
|
|
164
|
+
* Instantiated per agent run, garbage collected when the run ends.
|
|
165
|
+
* @private
|
|
166
|
+
* @since 2.46.0
|
|
167
|
+
*/
|
|
168
|
+
this._scratchpadManager = new ScratchpadManager();
|
|
161
169
|
/**
|
|
162
170
|
* Effective actions available to this agent after applying actionChanges.
|
|
163
171
|
* Populated during gatherPromptTemplateData() and used for validation in executeActionsStep().
|
|
@@ -818,6 +826,8 @@ export class BaseAgent {
|
|
|
818
826
|
if (params.convertUIMarkupToPlainText !== false) {
|
|
819
827
|
this.convertUIMarkupInMessages(wrappedParams.conversationMessages);
|
|
820
828
|
}
|
|
829
|
+
// Reset scratchpad for each new execution (ephemeral per run)
|
|
830
|
+
this._scratchpadManager.Clear();
|
|
821
831
|
await this.initializeStartingPayload(wrappedParams);
|
|
822
832
|
// Check for cancellation at start
|
|
823
833
|
if (params.cancellationToken?.aborted) {
|
|
@@ -1506,6 +1516,16 @@ export class BaseAgent {
|
|
|
1506
1516
|
agentId: params.agent.ID,
|
|
1507
1517
|
agentRunId: this._agentRun?.ID
|
|
1508
1518
|
});
|
|
1519
|
+
// Inject scratchpad template variables if scratchpad is enabled
|
|
1520
|
+
if (promptParams.data) {
|
|
1521
|
+
const agentTypePromptParams = promptParams.data.__agentTypePromptParams;
|
|
1522
|
+
const scratchpadEnabled = agentTypePromptParams?.includeScratchpadDocs !== false;
|
|
1523
|
+
if (scratchpadEnabled && this._scratchpadManager) {
|
|
1524
|
+
promptParams.data['_SCRATCHPAD_NOTES'] = this._scratchpadManager.GetNotes() || '_(no notes yet)_';
|
|
1525
|
+
promptParams.data['_SCRATCHPAD_TASKS'] = this._scratchpadManager.ToPromptString();
|
|
1526
|
+
promptParams.data['_SCRATCHPAD_TASK_SUMMARY'] = this._scratchpadManager.GetTaskSummary();
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1509
1529
|
// Only set up child prompts if we have a system prompt
|
|
1510
1530
|
if (systemPrompt) {
|
|
1511
1531
|
// Setup child prompt parameters
|
|
@@ -2879,24 +2899,21 @@ The context is now within limits. Please retry your request with the recovered c
|
|
|
2879
2899
|
* @protected
|
|
2880
2900
|
*/
|
|
2881
2901
|
createActionResultMessage(actions, results) {
|
|
2882
|
-
const
|
|
2902
|
+
const actionSummaries = actions.map((action, index) => {
|
|
2883
2903
|
const result = results[index];
|
|
2884
|
-
const outputParams = result.Params?.filter(
|
|
2904
|
+
const outputParams = result.Params?.filter(p => p.Type === 'Output' || p.Type === 'Both') || [];
|
|
2885
2905
|
return {
|
|
2886
2906
|
actionName: action.name,
|
|
2887
2907
|
success: result.Success,
|
|
2908
|
+
params: outputParams,
|
|
2888
2909
|
resultCode: result.Result?.ResultCode || 'N/A',
|
|
2889
|
-
message: result.Message ||
|
|
2910
|
+
message: result.Message || '(no message)',
|
|
2890
2911
|
aiDirectives: result.AIDirectives,
|
|
2891
|
-
outputs: outputParams.reduce((acc, param) => {
|
|
2892
|
-
acc[param.Name] = param.Value;
|
|
2893
|
-
return acc;
|
|
2894
|
-
}, {})
|
|
2895
2912
|
};
|
|
2896
2913
|
});
|
|
2897
2914
|
return {
|
|
2898
2915
|
role: 'user',
|
|
2899
|
-
content: `Action results:\n${
|
|
2916
|
+
content: `Action results:\n${this.formatActionResultsAsMarkdown(actionSummaries)}`
|
|
2900
2917
|
};
|
|
2901
2918
|
}
|
|
2902
2919
|
/**
|
|
@@ -2910,7 +2927,7 @@ The context is now within limits. Please retry your request with the recovered c
|
|
|
2910
2927
|
createSubAgentResultMessage(subAgent, result) {
|
|
2911
2928
|
return {
|
|
2912
2929
|
role: 'user',
|
|
2913
|
-
content:
|
|
2930
|
+
content: this.formatSubAgentResultAsMarkdown(subAgent.name, result)
|
|
2914
2931
|
};
|
|
2915
2932
|
}
|
|
2916
2933
|
/**
|
|
@@ -3068,7 +3085,8 @@ The context is now within limits. Please retry your request with the recovered c
|
|
|
3068
3085
|
responseForms: true,
|
|
3069
3086
|
commands: true,
|
|
3070
3087
|
forEach: true,
|
|
3071
|
-
while: true
|
|
3088
|
+
while: true,
|
|
3089
|
+
scratchpad: true
|
|
3072
3090
|
};
|
|
3073
3091
|
}
|
|
3074
3092
|
const responseType = params.includeResponseTypeDefinition;
|
|
@@ -3078,7 +3096,8 @@ The context is now within limits. Please retry your request with the recovered c
|
|
|
3078
3096
|
{ docsFlag: 'includeResponseFormDocs', responseTypeKey: 'responseForms' },
|
|
3079
3097
|
{ docsFlag: 'includeCommandDocs', responseTypeKey: 'commands' },
|
|
3080
3098
|
{ docsFlag: 'includeForEachDocs', responseTypeKey: 'forEach' },
|
|
3081
|
-
{ docsFlag: 'includeWhileDocs', responseTypeKey: 'while' }
|
|
3099
|
+
{ docsFlag: 'includeWhileDocs', responseTypeKey: 'while' },
|
|
3100
|
+
{ docsFlag: 'includeScratchpadDocs', responseTypeKey: 'scratchpad' }
|
|
3082
3101
|
];
|
|
3083
3102
|
for (const { docsFlag, responseTypeKey } of alignmentMappings) {
|
|
3084
3103
|
// Check if the user explicitly set this response type property
|
|
@@ -3370,27 +3389,20 @@ The context is now within limits. Please retry your request with the recovered c
|
|
|
3370
3389
|
}
|
|
3371
3390
|
}
|
|
3372
3391
|
/**
|
|
3373
|
-
* Formats sub-agent details for inclusion in prompt context.
|
|
3392
|
+
* Formats sub-agent details as compact markdown for inclusion in prompt context.
|
|
3374
3393
|
*
|
|
3375
3394
|
* @param {MJAIAgentEntityExtended[]} subAgents - Array of sub-agent entities
|
|
3376
|
-
* @returns {string}
|
|
3395
|
+
* @returns {string} Markdown formatted string with sub-agent details
|
|
3377
3396
|
* @private
|
|
3378
3397
|
*/
|
|
3379
3398
|
formatSubAgentDetails(subAgents) {
|
|
3380
|
-
return
|
|
3381
|
-
|
|
3382
|
-
Name: sa.Name,
|
|
3383
|
-
Description: sa.Description
|
|
3384
|
-
};
|
|
3399
|
+
return subAgents.map(sa => {
|
|
3400
|
+
let line = `- **${sa.Name}** — ${sa.Description}`;
|
|
3385
3401
|
if (sa.ExecutionMode !== 'Sequential') {
|
|
3386
|
-
|
|
3387
|
-
// that are sequential and the order is implied via the array order
|
|
3388
|
-
// saves tokens
|
|
3389
|
-
result['ExecutionMode'] = sa.ExecutionMode;
|
|
3390
|
-
result['ExecutionOrder'] = sa.ExecutionOrder;
|
|
3402
|
+
line += ` _(${sa.ExecutionMode}, order: ${sa.ExecutionOrder})_`;
|
|
3391
3403
|
}
|
|
3392
|
-
return
|
|
3393
|
-
})
|
|
3404
|
+
return line;
|
|
3405
|
+
}).join('\n');
|
|
3394
3406
|
}
|
|
3395
3407
|
/**
|
|
3396
3408
|
* Utility method to get agent prompt parameters for a given agent. This gets the
|
|
@@ -3422,53 +3434,178 @@ The context is now within limits. Please retry your request with the recovered c
|
|
|
3422
3434
|
})), null, 2);
|
|
3423
3435
|
}
|
|
3424
3436
|
/**
|
|
3425
|
-
* Formats action details for inclusion in prompt context.
|
|
3437
|
+
* Formats action details as compact markdown for inclusion in prompt context.
|
|
3438
|
+
* Produces ~75% fewer tokens than the previous JSON format while preserving
|
|
3439
|
+
* all information the LLM needs to invoke actions correctly.
|
|
3426
3440
|
*
|
|
3427
3441
|
* @param {MJActionEntityExtended[]} actions - Array of action entities
|
|
3428
|
-
* @returns {string}
|
|
3442
|
+
* @returns {string} Markdown formatted string with action details
|
|
3429
3443
|
* @private
|
|
3430
3444
|
*/
|
|
3431
3445
|
formatActionDetails(actions) {
|
|
3432
|
-
return
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3446
|
+
return actions.map(action => {
|
|
3447
|
+
const lines = [];
|
|
3448
|
+
lines.push(`### ${action.Name}`);
|
|
3449
|
+
lines.push(action.Description);
|
|
3450
|
+
const inputParams = action.Params
|
|
3451
|
+
.filter(p => {
|
|
3452
|
+
const t = p.Type.trim().toLowerCase();
|
|
3453
|
+
return t === 'input' || t === 'both';
|
|
3454
|
+
});
|
|
3455
|
+
const outputParams = action.Params
|
|
3456
|
+
.filter(p => {
|
|
3457
|
+
const t = p.Type.trim().toLowerCase();
|
|
3458
|
+
return t === 'output' || t === 'both';
|
|
3459
|
+
});
|
|
3460
|
+
if (inputParams.length > 0) {
|
|
3461
|
+
lines.push(`**Input:** ${inputParams.map(p => this.formatActionParameter(p)).join(', ')}`);
|
|
3462
|
+
}
|
|
3463
|
+
if (outputParams.length > 0) {
|
|
3464
|
+
lines.push(`**Output:** ${outputParams.map(p => this.formatActionParameter(p)).join(', ')}`);
|
|
3465
|
+
}
|
|
3466
|
+
if (action.ResultCodes.length > 0) {
|
|
3467
|
+
const rcParts = action.ResultCodes.map(rc => {
|
|
3468
|
+
const marker = rc.IsSuccess ? '✓' : '✗';
|
|
3469
|
+
const desc = rc.Description && rc.Description.toLowerCase() !== rc.ResultCode.toLowerCase()
|
|
3470
|
+
? ` ${rc.Description}`
|
|
3471
|
+
: '';
|
|
3472
|
+
return `${rc.ResultCode} ${marker}${desc}`;
|
|
3473
|
+
});
|
|
3474
|
+
lines.push(`**Results:** ${rcParts.join(' · ')}`);
|
|
3475
|
+
}
|
|
3476
|
+
return lines.join('\n');
|
|
3477
|
+
}).join('\n\n');
|
|
3454
3478
|
}
|
|
3455
3479
|
/**
|
|
3456
|
-
* Formats a single action parameter
|
|
3480
|
+
* Formats a single action parameter as a compact inline string.
|
|
3481
|
+
* Uses \* suffix for required, (array) when IsArray, and only shows
|
|
3482
|
+
* ValueType when it is not Scalar/Other.
|
|
3457
3483
|
*
|
|
3458
|
-
* @param {
|
|
3459
|
-
* @returns {
|
|
3484
|
+
* @param {MJActionParamEntity} param - The action parameter to format
|
|
3485
|
+
* @returns {string} Compact inline string, e.g. `` `To`\* — Email address ``
|
|
3460
3486
|
* @private
|
|
3461
3487
|
*/
|
|
3462
3488
|
formatActionParameter(param) {
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
}
|
|
3489
|
+
const requiredMarker = param.IsRequired ? '\\*' : '';
|
|
3490
|
+
const parts = [];
|
|
3491
|
+
if (param.IsArray) {
|
|
3492
|
+
parts.push('array');
|
|
3493
|
+
}
|
|
3494
|
+
const vt = param.ValueType?.trim();
|
|
3495
|
+
if (vt && vt !== 'Scalar' && vt !== 'Other') {
|
|
3496
|
+
parts.push(vt);
|
|
3497
|
+
}
|
|
3498
|
+
const suffix = parts.length > 0 ? ` (${parts.join(', ')})` : '';
|
|
3499
|
+
let defaultStr = '';
|
|
3500
|
+
if (param.DefaultValue != null && param.DefaultValue !== '') {
|
|
3501
|
+
defaultStr = ` (default: ${JSON.stringify(param.DefaultValue)})`;
|
|
3502
|
+
}
|
|
3503
|
+
const desc = param.Description ? ` — ${param.Description}` : '';
|
|
3504
|
+
return `\`${param.Name}\`${requiredMarker}${suffix}${desc}${defaultStr}`;
|
|
3505
|
+
}
|
|
3506
|
+
/**
|
|
3507
|
+
* Formats an array of action result summaries as compact markdown instead of
|
|
3508
|
+
* pretty-printed JSON. This typically saves 60-70 % of the tokens that the
|
|
3509
|
+
* old `JSON.stringify(actionSummaries, null, 2)` approach consumed, while
|
|
3510
|
+
* remaining equally parseable by LLMs.
|
|
3511
|
+
*
|
|
3512
|
+
* Format per action:
|
|
3513
|
+
* ```
|
|
3514
|
+
* ## ActionName ✓ (or ✗ for failure)
|
|
3515
|
+
* **Result:** RESULT_CODE — human message
|
|
3516
|
+
* **Output:**
|
|
3517
|
+
* • `ParamName`: value
|
|
3518
|
+
* ```
|
|
3519
|
+
*
|
|
3520
|
+
* AI directives are intentionally omitted here because they are surfaced
|
|
3521
|
+
* as a separate high-priority message (see the caller).
|
|
3522
|
+
*
|
|
3523
|
+
* @param actionSummaries - The action summary objects built by executeActionStep
|
|
3524
|
+
* @returns Compact markdown string
|
|
3525
|
+
* @private
|
|
3526
|
+
*/
|
|
3527
|
+
formatActionResultsAsMarkdown(actionSummaries) {
|
|
3528
|
+
return actionSummaries.map(a => {
|
|
3529
|
+
const marker = a.success ? '✓' : '✗';
|
|
3530
|
+
const lines = [];
|
|
3531
|
+
lines.push(`## ${a.actionName} ${marker}`);
|
|
3532
|
+
lines.push(`**Result:** ${a.resultCode} — ${a.message || '(no message)'}`);
|
|
3533
|
+
// Format output params as bullet list
|
|
3534
|
+
if (a.params && a.params.length > 0) {
|
|
3535
|
+
lines.push('**Output:**');
|
|
3536
|
+
for (const p of a.params) {
|
|
3537
|
+
lines.push(`• \`${p.Name}\`: ${this.formatParamValueForResult(p.Value)}`);
|
|
3538
|
+
}
|
|
3539
|
+
}
|
|
3540
|
+
return lines.join('\n');
|
|
3541
|
+
}).join('\n\n');
|
|
3542
|
+
}
|
|
3543
|
+
/**
|
|
3544
|
+
* Formats an ExecuteAgentResult as compact markdown for inclusion in
|
|
3545
|
+
* conversation messages. Extracts only the fields that are meaningful
|
|
3546
|
+
* to the calling agent (success, status, error, and payload) and
|
|
3547
|
+
* presents them in a concise format.
|
|
3548
|
+
*
|
|
3549
|
+
* @param subAgentName - Display name of the sub-agent
|
|
3550
|
+
* @param result - The execution result from the sub-agent
|
|
3551
|
+
* @returns Compact markdown string
|
|
3552
|
+
* @private
|
|
3553
|
+
*/
|
|
3554
|
+
formatSubAgentResultAsMarkdown(subAgentName, result) {
|
|
3555
|
+
const marker = result.success ? '✓' : '✗';
|
|
3556
|
+
const lines = [];
|
|
3557
|
+
lines.push(`## Sub-agent: ${subAgentName} ${marker}`);
|
|
3558
|
+
// Status / error from the agent run
|
|
3559
|
+
const status = result.agentRun?.Status || (result.success ? 'Completed' : 'Failed');
|
|
3560
|
+
lines.push(`**Status:** ${status}`);
|
|
3561
|
+
if (!result.success && result.agentRun?.ErrorMessage) {
|
|
3562
|
+
lines.push(`**Error:** ${result.agentRun.ErrorMessage}`);
|
|
3563
|
+
}
|
|
3564
|
+
// Payload — the actual result data the parent agent cares about
|
|
3565
|
+
if (result.payload != null) {
|
|
3566
|
+
const payloadStr = typeof result.payload === 'string'
|
|
3567
|
+
? result.payload
|
|
3568
|
+
: JSON.stringify(result.payload);
|
|
3569
|
+
// For very large payloads, truncate to keep context manageable
|
|
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
|
+
}
|
|
3576
|
+
}
|
|
3577
|
+
return lines.join('\n');
|
|
3578
|
+
}
|
|
3579
|
+
/**
|
|
3580
|
+
* Formats a single output parameter value for inclusion in action result
|
|
3581
|
+
* markdown. Scalars are shown inline with backtick formatting; objects and
|
|
3582
|
+
* arrays use compact (single-line) JSON to avoid the indentation overhead
|
|
3583
|
+
* of pretty-printed JSON. Very long values are truncated.
|
|
3584
|
+
*
|
|
3585
|
+
* @param value - The parameter value (any type)
|
|
3586
|
+
* @param maxLength - Maximum character length before truncation (default 500)
|
|
3587
|
+
* @returns Formatted string
|
|
3588
|
+
* @private
|
|
3589
|
+
*/
|
|
3590
|
+
formatParamValueForResult(value, maxLength = 500) {
|
|
3591
|
+
if (value === null || value === undefined) {
|
|
3592
|
+
return '`null`';
|
|
3593
|
+
}
|
|
3594
|
+
if (typeof value === 'boolean' || typeof value === 'number') {
|
|
3595
|
+
return `\`${String(value)}\``;
|
|
3596
|
+
}
|
|
3597
|
+
let stringValue;
|
|
3598
|
+
if (typeof value === 'string') {
|
|
3599
|
+
stringValue = value;
|
|
3600
|
+
}
|
|
3601
|
+
else {
|
|
3602
|
+
// Compact JSON (no pretty-printing) for objects/arrays
|
|
3603
|
+
stringValue = JSON.stringify(value);
|
|
3604
|
+
}
|
|
3605
|
+
if (stringValue.length > maxLength) {
|
|
3606
|
+
return `${stringValue.substring(0, maxLength)}…`;
|
|
3607
|
+
}
|
|
3608
|
+
return stringValue;
|
|
3472
3609
|
}
|
|
3473
3610
|
/**
|
|
3474
3611
|
* Formats a parameter value for display in action execution messages.
|
|
@@ -4203,7 +4340,8 @@ The context is now within limits. Please retry your request with the recovered c
|
|
|
4203
4340
|
const promptToUse = await this.AgentTypeInstance.GetPromptForStep(params, config, previousDecision?.newPayload || previousDecision?.previousPayload, this.AgentTypeState, previousDecision);
|
|
4204
4341
|
const promptId = promptToUse?.ID;
|
|
4205
4342
|
const promptName = promptToUse?.Name;
|
|
4206
|
-
// Prepare input data for the step
|
|
4343
|
+
// Prepare input data for the step (includes scratchpad snapshot before LLM response)
|
|
4344
|
+
const scratchpadSnapshotBeforeStep = this._scratchpadManager.HasContent() ? this._scratchpadManager.ToJSON() : undefined;
|
|
4207
4345
|
const inputData = {
|
|
4208
4346
|
promptId: promptId,
|
|
4209
4347
|
promptName: promptName,
|
|
@@ -4213,6 +4351,7 @@ The context is now within limits. Please retry your request with the recovered c
|
|
|
4213
4351
|
instructions: previousDecision.retryInstructions
|
|
4214
4352
|
} : undefined,
|
|
4215
4353
|
conversationMessages: params.conversationMessages,
|
|
4354
|
+
...(scratchpadSnapshotBeforeStep && { scratchpad: scratchpadSnapshotBeforeStep }),
|
|
4216
4355
|
};
|
|
4217
4356
|
// Prepare prompt parameters
|
|
4218
4357
|
const payload = previousDecision?.newPayload || params.payload;
|
|
@@ -4376,6 +4515,18 @@ The context is now within limits. Please retry your request with the recovered c
|
|
|
4376
4515
|
// Set the final payload - the changeResult already respects the allowed paths
|
|
4377
4516
|
finalPayload = changeResult.result;
|
|
4378
4517
|
}
|
|
4518
|
+
// Apply scratchpad changes if provided (zero turn cost — processed inline)
|
|
4519
|
+
if (initialNextStep.scratchpad) {
|
|
4520
|
+
this._scratchpadManager.ApplyScratchpadChanges(initialNextStep.scratchpad);
|
|
4521
|
+
// Enforce task limit using the default (50) — the prompt params were already
|
|
4522
|
+
// used during prompt preparation. We use a simple default here to avoid
|
|
4523
|
+
// re-fetching prompt params just for this value.
|
|
4524
|
+
const maxTasks = 50;
|
|
4525
|
+
const pruned = this._scratchpadManager.EnforceTaskLimit(maxTasks);
|
|
4526
|
+
if (pruned > 0 && (params.verbose === true || IsVerboseLoggingEnabled())) {
|
|
4527
|
+
LogStatus(`Scratchpad: pruned ${pruned} completed tasks (limit: ${maxTasks})`);
|
|
4528
|
+
}
|
|
4529
|
+
}
|
|
4379
4530
|
// now that we have processed the payload, we can process the next step which does validation and changes the next step if
|
|
4380
4531
|
// validation fails
|
|
4381
4532
|
const updatedNextStep = await this.processNextStep(initialNextStep, params, config.agentType, promptResult, finalPayload, stepEntity);
|
|
@@ -4394,6 +4545,10 @@ The context is now within limits. Please retry your request with the recovered c
|
|
|
4394
4545
|
...(currentStepPayloadChangeResult && {
|
|
4395
4546
|
payloadChangeResult: currentStepPayloadChangeResult
|
|
4396
4547
|
}),
|
|
4548
|
+
// Include scratchpad snapshot after changes for audit/training data
|
|
4549
|
+
...(this._scratchpadManager.HasContent() && {
|
|
4550
|
+
scratchpad: this._scratchpadManager.ToJSON()
|
|
4551
|
+
}),
|
|
4397
4552
|
// Include memory attribution for observability
|
|
4398
4553
|
// This tracks which notes/examples were injected and influenced this step
|
|
4399
4554
|
...(this._injectedMemory && (this._injectedMemory.notes.length > 0 || this._injectedMemory.examples.length > 0) && {
|
|
@@ -5483,14 +5638,17 @@ The context is now within limits. Please retry your request with the recovered c
|
|
|
5483
5638
|
success: result.success,
|
|
5484
5639
|
params: sanitizedParams,
|
|
5485
5640
|
resultCode: actionResult?.Result?.ResultCode || (result.success ? 'SUCCESS' : 'ERROR'),
|
|
5486
|
-
message: result.success ? actionResult?.Message || 'Action completed' : result.error,
|
|
5641
|
+
message: result.success ? actionResult?.Message || 'Action completed' : result.error || 'Unknown error',
|
|
5487
5642
|
aiDirectives: result.success ? actionResult?.AIDirectives : undefined
|
|
5488
5643
|
};
|
|
5489
5644
|
});
|
|
5490
5645
|
// Check if any actions failed
|
|
5491
5646
|
const failedActions = actionSummaries.filter(a => !a.success);
|
|
5492
|
-
// Add user message with the results
|
|
5493
|
-
const
|
|
5647
|
+
// Add user message with the results — compact markdown instead of JSON
|
|
5648
|
+
const header = failedActions.length > 0
|
|
5649
|
+
? `${failedActions.length} of ${actionSummaries.length} action(s) failed:`
|
|
5650
|
+
: `Action results:`;
|
|
5651
|
+
const resultsMessage = `${header}\n${this.formatActionResultsAsMarkdown(actionSummaries)}`;
|
|
5494
5652
|
// Build metadata from AI Agent Actions configuration
|
|
5495
5653
|
// If multiple actions, use the most restrictive (shortest) expiration settings
|
|
5496
5654
|
let metadata;
|
|
@@ -7046,8 +7204,16 @@ The context is now within limits. Please retry your request with the recovered c
|
|
|
7046
7204
|
// and we trim whitespace first
|
|
7047
7205
|
const trimmedValue = value.trim();
|
|
7048
7206
|
if (trimmedValue.startsWith('{{') && trimmedValue.endsWith('}}')) {
|
|
7207
|
+
// Single-variable case: "{{city.name}}" — strip wrappers, fall through to
|
|
7208
|
+
// single-variable resolution below which can return non-string types (objects, numbers)
|
|
7049
7209
|
value = trimmedValue.substring(2, trimmedValue.length - 2).trim();
|
|
7050
7210
|
}
|
|
7211
|
+
else if (trimmedValue.includes('{{')) {
|
|
7212
|
+
// Inline template interpolation: "text {{var.prop}} more {{var2.field}}"
|
|
7213
|
+
// The string contains embedded {{}} expressions mixed with literal text.
|
|
7214
|
+
// Each expression is resolved individually and stringified back into the template.
|
|
7215
|
+
return this.resolveInlineTemplateExpressions(trimmedValue, context, itemVariable);
|
|
7216
|
+
}
|
|
7051
7217
|
// Check itemVariable reference (the custom loop variable name like "entityName" or "user")
|
|
7052
7218
|
// This handles both direct references and path-based property access
|
|
7053
7219
|
const ivToLower = itemVariable?.trim().toLowerCase();
|
|
@@ -7079,6 +7245,32 @@ The context is now within limits. Please retry your request with the recovered c
|
|
|
7079
7245
|
// Static value - no variable reference found, return as literal string
|
|
7080
7246
|
return value;
|
|
7081
7247
|
}
|
|
7248
|
+
/**
|
|
7249
|
+
* Resolves multiple {{expression}} placeholders embedded within a literal string.
|
|
7250
|
+
*
|
|
7251
|
+
* Unlike the single-variable path (which can return objects/numbers), this always
|
|
7252
|
+
* returns a string because the resolved values are interpolated back into surrounding text.
|
|
7253
|
+
*
|
|
7254
|
+
* @example
|
|
7255
|
+
* // Given itemVariable="cityInfo", context.item={city:"Tokyo", country:"Japan"}
|
|
7256
|
+
* resolveInlineTemplateExpressions(
|
|
7257
|
+
* "largest company in {{cityInfo.city}} {{cityInfo.country}}",
|
|
7258
|
+
* context, "cityInfo"
|
|
7259
|
+
* )
|
|
7260
|
+
* // → "largest company in Tokyo Japan"
|
|
7261
|
+
*/
|
|
7262
|
+
resolveInlineTemplateExpressions(template, context, itemVariable) {
|
|
7263
|
+
const expressionPattern = /\{\{\s*([^}]+?)\s*\}\}/g;
|
|
7264
|
+
return template.replace(expressionPattern, (_match, expr) => {
|
|
7265
|
+
// Resolve each expression using the single-variable path (without {{ }} wrappers)
|
|
7266
|
+
const resolved = this.resolveValueFromContext(expr.trim(), context, itemVariable);
|
|
7267
|
+
// If the expression didn't resolve (returned unchanged), keep original {{ }} for transparency
|
|
7268
|
+
if (resolved === expr.trim()) {
|
|
7269
|
+
return _match;
|
|
7270
|
+
}
|
|
7271
|
+
return String(resolved ?? '');
|
|
7272
|
+
});
|
|
7273
|
+
}
|
|
7082
7274
|
/**
|
|
7083
7275
|
* Helper to get value from nested object path - extracts from LoopAgentType
|
|
7084
7276
|
*/
|