@memberjunction/ai-agents 3.3.0 → 4.0.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.
Files changed (97) hide show
  1. package/dist/AgentDataPreloader.d.ts +117 -1
  2. package/dist/AgentDataPreloader.d.ts.map +1 -1
  3. package/dist/AgentDataPreloader.js +156 -58
  4. package/dist/AgentDataPreloader.js.map +1 -1
  5. package/dist/AgentRunner.d.ts +212 -0
  6. package/dist/AgentRunner.d.ts.map +1 -1
  7. package/dist/AgentRunner.js +354 -103
  8. package/dist/AgentRunner.js.map +1 -1
  9. package/dist/PayloadChangeAnalyzer.d.ts +68 -0
  10. package/dist/PayloadChangeAnalyzer.d.ts.map +1 -1
  11. package/dist/PayloadChangeAnalyzer.js +68 -32
  12. package/dist/PayloadChangeAnalyzer.js.map +1 -1
  13. package/dist/PayloadFeedbackManager.d.ts +57 -1
  14. package/dist/PayloadFeedbackManager.d.ts.map +1 -1
  15. package/dist/PayloadFeedbackManager.js +66 -21
  16. package/dist/PayloadFeedbackManager.js.map +1 -1
  17. package/dist/PayloadManager.d.ts +286 -1
  18. package/dist/PayloadManager.d.ts.map +1 -1
  19. package/dist/PayloadManager.js +423 -50
  20. package/dist/PayloadManager.js.map +1 -1
  21. package/dist/__tests__/action-changes.test.d.ts +13 -0
  22. package/dist/__tests__/action-changes.test.d.ts.map +1 -1
  23. package/dist/__tests__/action-changes.test.js +57 -4
  24. package/dist/__tests__/action-changes.test.js.map +1 -1
  25. package/dist/__tests__/agent-memory-features.test.d.ts +52 -0
  26. package/dist/__tests__/agent-memory-features.test.d.ts.map +1 -1
  27. package/dist/__tests__/agent-memory-features.test.js +96 -13
  28. package/dist/__tests__/agent-memory-features.test.js.map +1 -1
  29. package/dist/__tests__/agent-type-prompt-params.test.d.ts +12 -0
  30. package/dist/__tests__/agent-type-prompt-params.test.d.ts.map +1 -1
  31. package/dist/__tests__/agent-type-prompt-params.test.js +112 -20
  32. package/dist/__tests__/agent-type-prompt-params.test.js.map +1 -1
  33. package/dist/__tests__/chat-handling-option.test.d.ts +26 -0
  34. package/dist/__tests__/chat-handling-option.test.d.ts.map +1 -1
  35. package/dist/__tests__/chat-handling-option.test.js +41 -2
  36. package/dist/__tests__/chat-handling-option.test.js.map +1 -1
  37. package/dist/agent-context-injector.d.ts +114 -0
  38. package/dist/agent-context-injector.d.ts.map +1 -1
  39. package/dist/agent-context-injector.js +138 -19
  40. package/dist/agent-context-injector.js.map +1 -1
  41. package/dist/agent-types/base-agent-type.d.ts +354 -0
  42. package/dist/agent-types/base-agent-type.d.ts.map +1 -1
  43. package/dist/agent-types/base-agent-type.js +288 -17
  44. package/dist/agent-types/base-agent-type.js.map +1 -1
  45. package/dist/agent-types/flow-agent-type.d.ts +328 -2
  46. package/dist/agent-types/flow-agent-type.d.ts.map +1 -1
  47. package/dist/agent-types/flow-agent-type.js +503 -63
  48. package/dist/agent-types/flow-agent-type.js.map +1 -1
  49. package/dist/agent-types/index.d.ts +14 -3
  50. package/dist/agent-types/index.d.ts.map +1 -1
  51. package/dist/agent-types/index.js +14 -23
  52. package/dist/agent-types/index.js.map +1 -1
  53. package/dist/agent-types/loop-agent-prompt-params.d.ts +190 -0
  54. package/dist/agent-types/loop-agent-prompt-params.d.ts.map +1 -1
  55. package/dist/agent-types/loop-agent-prompt-params.js +23 -6
  56. package/dist/agent-types/loop-agent-prompt-params.js.map +1 -1
  57. package/dist/agent-types/loop-agent-response-type.d.ts +62 -0
  58. package/dist/agent-types/loop-agent-response-type.d.ts.map +1 -1
  59. package/dist/agent-types/loop-agent-response-type.js +3 -2
  60. package/dist/agent-types/loop-agent-response-type.js.map +1 -1
  61. package/dist/agent-types/loop-agent-type.d.ts +139 -2
  62. package/dist/agent-types/loop-agent-type.d.ts.map +1 -1
  63. package/dist/agent-types/loop-agent-type.js +207 -35
  64. package/dist/agent-types/loop-agent-type.js.map +1 -1
  65. package/dist/base-agent.d.ts +1344 -1
  66. package/dist/base-agent.d.ts.map +1 -1
  67. package/dist/base-agent.js +2282 -211
  68. package/dist/base-agent.js.map +1 -1
  69. package/dist/index.d.ts +24 -14
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.js +25 -35
  72. package/dist/index.js.map +1 -1
  73. package/dist/memory-cleanup-agent.d.ts +53 -1
  74. package/dist/memory-cleanup-agent.d.ts.map +1 -1
  75. package/dist/memory-cleanup-agent.js +89 -21
  76. package/dist/memory-cleanup-agent.js.map +1 -1
  77. package/dist/memory-manager-agent.d.ts +61 -1
  78. package/dist/memory-manager-agent.d.ts.map +1 -1
  79. package/dist/memory-manager-agent.js +260 -116
  80. package/dist/memory-manager-agent.js.map +1 -1
  81. package/dist/services/AgentEmbeddingService.d.ts +16 -0
  82. package/dist/services/AgentEmbeddingService.d.ts.map +1 -0
  83. package/dist/services/AgentEmbeddingService.js +158 -0
  84. package/dist/services/AgentEmbeddingService.js.map +1 -0
  85. package/dist/types/AgentMatchResult.d.ts +18 -0
  86. package/dist/types/AgentMatchResult.d.ts.map +1 -0
  87. package/dist/types/AgentMatchResult.js +3 -0
  88. package/dist/types/AgentMatchResult.js.map +1 -0
  89. package/dist/types/payload-operations.d.ts +51 -0
  90. package/dist/types/payload-operations.d.ts.map +1 -1
  91. package/dist/types/payload-operations.js +54 -15
  92. package/dist/types/payload-operations.js.map +1 -1
  93. package/dist/utils/ConversationMessageResolver.d.ts +79 -1
  94. package/dist/utils/ConversationMessageResolver.d.ts.map +1 -1
  95. package/dist/utils/ConversationMessageResolver.js +99 -9
  96. package/dist/utils/ConversationMessageResolver.js.map +1 -1
  97. package/package.json +20 -19
@@ -1,40 +1,77 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
25
- Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.PayloadManager = void 0;
27
- const core_1 = require("@memberjunction/core");
28
- const global_1 = require("@memberjunction/global");
29
- const _ = __importStar(require("lodash"));
30
- const PayloadChangeAnalyzer_1 = require("./PayloadChangeAnalyzer");
31
- const payload_operations_1 = require("./types/payload-operations");
32
- class PayloadManager {
1
+ /**
2
+ * @fileoverview Payload management for hierarchical agent execution.
3
+ *
4
+ * This module provides functionality to control which parts of a payload
5
+ * are accessible to sub-agents (downstream) and which parts they can
6
+ * modify (upstream). It supports JSON path-based access control with
7
+ * wildcards and nested path support.
8
+ *
9
+ * ## Key Features:
10
+ * - Path-based access control for sub-agent data isolation
11
+ * - Operation-level permissions (add, update, delete) per path
12
+ * - Automatic detection of suspicious payload changes
13
+ * - Human-readable diff generation for audit trails
14
+ * - Configurable warning thresholds for content changes
15
+ *
16
+ * ## Path Syntax:
17
+ * - Exact paths: "customer.name"
18
+ * - Wildcards: "customer.*" (all properties under customer)
19
+ * - Array indices: "items[0].price" or "items[*].price"
20
+ * - Deep wildcards: "**.id" (all id fields at any depth)
21
+ * - Root wildcard: "*" (entire payload)
22
+ * - Operation control: "path:add,update" (specific operations only)
23
+ *
24
+ * ## Change Detection Rules:
25
+ * - Content truncation: Warns when text reduced by >70%
26
+ * - Key removal: Flags when non-empty keys are removed
27
+ * - Type changes: Detects object→primitive conversions
28
+ * - Pattern anomalies: Identifies placeholder replacements
29
+ *
30
+ * @module @memberjunction/ai-agents
31
+ * @author MemberJunction.com
32
+ * @since 3.0.0
33
+ */
34
+ import { LogError, LogStatus } from '@memberjunction/core';
35
+ import { DeepDiffer } from '@memberjunction/global';
36
+ import _ from 'lodash';
37
+ import { PayloadChangeAnalyzer } from './PayloadChangeAnalyzer.js';
38
+ import { parsePathWithOperations, parsePathsWithOperations, isOperationAllowed } from './types/payload-operations.js';
39
+ /**
40
+ * Manages payload access control for agent hierarchies.
41
+ *
42
+ * This class handles extracting specific paths from payloads when sending
43
+ * data downstream to sub-agents, and merging results back upstream while
44
+ * respecting write permissions.
45
+ *
46
+ * Path syntax supports:
47
+ * - Exact paths: "customer.name"
48
+ * - Wildcards: "customer.*" (all properties under customer)
49
+ * - Array indices: "items[0].price" or "items[*].price"
50
+ * - Deep wildcards: "**.id" (all id fields at any depth)
51
+ * - Root wildcard: "*" (entire payload)
52
+ */
53
+ export class PayloadManager {
54
+ /**
55
+ * Extracts only the allowed paths from a payload for downstream transmission.
56
+ *
57
+ * @param fullPayload The complete payload object
58
+ * @param downstreamPaths Array of path patterns defining what to extract
59
+ * @returns A new object containing only the allowed paths
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * const payload = { customer: { id: 1, name: 'John', secret: 'xxx' }, order: { id: 2 } };
64
+ * const paths = ['customer.id', 'customer.name', 'order.*'];
65
+ * const result = extractDownstreamPayload(payload, paths);
66
+ * // Returns: { customer: { id: 1, name: 'John' }, order: { id: 2 } }
67
+ * ```
68
+ */
33
69
  extractDownstreamPayload(subAgentName, fullPayload, downstreamPaths) {
34
70
  if (!fullPayload)
35
71
  return null;
36
72
  if (!downstreamPaths || downstreamPaths.length === 0)
37
73
  return {};
74
+ // Handle wildcard - return everything
38
75
  if (downstreamPaths.includes('*')) {
39
76
  return _.cloneDeep(fullPayload);
40
77
  }
@@ -44,11 +81,28 @@ class PayloadManager {
44
81
  this.extractPathPattern(fullPayload, pathPattern, result);
45
82
  }
46
83
  catch (error) {
47
- (0, core_1.LogError)(`Failed to extract path pattern '${pathPattern}': ${error.message}`);
84
+ LogError(`Failed to extract path pattern '${pathPattern}': ${error.message}`);
48
85
  }
49
86
  }
50
87
  return result;
51
88
  }
89
+ /**
90
+ * Merges sub-agent results back into the parent payload, respecting write permissions.
91
+ *
92
+ * @param parentPayload The original parent payload
93
+ * @param subAgentPayload The payload returned by the sub-agent
94
+ * @param upstreamPaths Array of path patterns the sub-agent is allowed to write
95
+ * @returns A new merged payload object
96
+ *
97
+ * @example
98
+ * ```typescript
99
+ * const parent = { customer: { id: 1 }, analysis: { sentiment: null } };
100
+ * const subResult = { customer: { id: 2 }, analysis: { sentiment: 'positive', score: 0.9 } };
101
+ * const writePaths = ['analysis.*'];
102
+ * const merged = mergeUpstreamPayload(parent, subResult, writePaths);
103
+ * // Returns: { customer: { id: 1 }, analysis: { sentiment: 'positive', score: 0.9 } }
104
+ * ```
105
+ */
52
106
  mergeUpstreamPayload(subAgentName, parentPayload, subAgentPayload, upstreamPaths, verbose) {
53
107
  const blockedOperations = [];
54
108
  if (!parentPayload && !subAgentPayload) {
@@ -71,7 +125,7 @@ class PayloadManager {
71
125
  }
72
126
  if (!upstreamPaths || upstreamPaths.length === 0) {
73
127
  if (verbose) {
74
- (0, core_1.LogStatus)('Warning: No upstream paths specified - sub-agent changes will be ignored');
128
+ LogStatus('Warning: No upstream paths specified - sub-agent changes will be ignored');
75
129
  }
76
130
  return {
77
131
  result: parentPayload,
@@ -81,10 +135,13 @@ class PayloadManager {
81
135
  blockedOperations
82
136
  };
83
137
  }
138
+ // Start with a deep clone of the parent payload
84
139
  const result = _.cloneDeep(parentPayload || {});
85
140
  const counts = { additions: 0, updates: 0, deletions: 0 };
141
+ // Handle wildcard - merge everything
86
142
  if (upstreamPaths.includes('*')) {
87
143
  const merged = this.deepMerge(result, subAgentPayload);
144
+ // Count changes
88
145
  this.countChanges(parentPayload, merged, counts);
89
146
  return {
90
147
  result: merged,
@@ -94,6 +151,7 @@ class PayloadManager {
94
151
  blockedOperations
95
152
  };
96
153
  }
154
+ // Check each path in the sub-agent payload
97
155
  const mergeResult = this.mergeAllowedPaths(result, subAgentPayload, upstreamPaths, subAgentName, verbose);
98
156
  return {
99
157
  result: result,
@@ -103,27 +161,42 @@ class PayloadManager {
103
161
  blockedOperations: mergeResult.blockedOperations
104
162
  };
105
163
  }
164
+ /**
165
+ * Extracts a path pattern from source object into destination.
166
+ *
167
+ * @private
168
+ */
106
169
  extractPathPattern(source, pathPattern, destination) {
170
+ // Handle deep wildcards (**)
107
171
  if (pathPattern.includes('**')) {
108
172
  this.extractDeepWildcard(source, pathPattern, destination);
109
173
  return;
110
174
  }
175
+ // Handle regular paths with potential wildcards
111
176
  const pathParts = this.parsePathParts(pathPattern);
112
177
  this.extractPath(source, pathParts, destination, []);
113
178
  }
179
+ /**
180
+ * Recursively extracts paths based on parsed path parts.
181
+ *
182
+ * @private
183
+ */
114
184
  extractPath(source, pathParts, destination, currentPath) {
115
185
  if (!source || pathParts.length === 0)
116
186
  return;
117
187
  const [currentPart, ...remainingParts] = pathParts;
188
+ // Handle wildcards
118
189
  if (currentPart === '*') {
119
190
  if (_.isObject(source) && !_.isArray(source)) {
120
191
  for (const key in source) {
121
192
  if (source.hasOwnProperty(key)) {
122
193
  const newPath = [...currentPath, key];
123
194
  if (remainingParts.length === 0) {
195
+ // Terminal wildcard - copy the value
124
196
  _.set(destination, newPath, _.cloneDeep(source[key]));
125
197
  }
126
198
  else {
199
+ // Non-terminal wildcard - continue recursion
127
200
  this.extractPath(source[key], remainingParts, destination, newPath);
128
201
  }
129
202
  }
@@ -131,20 +204,29 @@ class PayloadManager {
131
204
  }
132
205
  return;
133
206
  }
207
+ // Handle array notation
134
208
  if (currentPart.includes('[')) {
135
209
  this.extractArrayPath(source, currentPart, remainingParts, destination, currentPath);
136
210
  return;
137
211
  }
212
+ // Handle regular property
138
213
  if (source.hasOwnProperty(currentPart)) {
139
214
  const newPath = [...currentPath, currentPart];
140
215
  if (remainingParts.length === 0) {
216
+ // Terminal property - copy the value
141
217
  _.set(destination, newPath, _.cloneDeep(source[currentPart]));
142
218
  }
143
219
  else {
220
+ // Non-terminal property - continue recursion
144
221
  this.extractPath(source[currentPart], remainingParts, destination, newPath);
145
222
  }
146
223
  }
147
224
  }
225
+ /**
226
+ * Handles array path extraction (e.g., items[0] or items[*]).
227
+ *
228
+ * @private
229
+ */
148
230
  extractArrayPath(source, arrayPart, remainingParts, destination, currentPath) {
149
231
  const match = arrayPart.match(/^([^[]+)\[([^\]]+)\]$/);
150
232
  if (!match)
@@ -155,6 +237,7 @@ class PayloadManager {
155
237
  return;
156
238
  const newPath = [...currentPath, propertyName];
157
239
  if (indexPart === '*') {
240
+ // Extract all array elements
158
241
  const destArray = [];
159
242
  for (let i = 0; i < sourceArray.length; i++) {
160
243
  if (remainingParts.length === 0) {
@@ -173,6 +256,7 @@ class PayloadManager {
173
256
  }
174
257
  }
175
258
  else {
259
+ // Extract specific index
176
260
  const index = parseInt(indexPart, 10);
177
261
  if (!isNaN(index) && index >= 0 && index < sourceArray.length) {
178
262
  if (remainingParts.length === 0) {
@@ -184,6 +268,11 @@ class PayloadManager {
184
268
  }
185
269
  }
186
270
  }
271
+ /**
272
+ * Handles deep wildcard extraction (e.g., **.id).
273
+ *
274
+ * @private
275
+ */
187
276
  extractDeepWildcard(source, pathPattern, destination) {
188
277
  const parts = pathPattern.split('**.');
189
278
  if (parts.length !== 2 || parts[0] !== '')
@@ -210,19 +299,31 @@ class PayloadManager {
210
299
  };
211
300
  extractRecursive(source);
212
301
  }
302
+ /**
303
+ * Merges allowed paths from sub-agent payload into result.
304
+ *
305
+ * @private
306
+ */
213
307
  mergeAllowedPaths(result, subAgentPayload, upstreamPaths, subAgentName, verbose) {
308
+ // Get all paths from sub-agent payload
214
309
  const subAgentPaths = this.getAllPaths(subAgentPayload);
310
+ // Track unauthorized changes for consolidated warning
215
311
  const unauthorizedChanges = [];
312
+ // Check each path against allowed patterns
216
313
  for (const actualPath of subAgentPaths) {
217
314
  const subAgentValue = _.get(subAgentPayload, actualPath);
218
315
  const originalValue = _.get(result, actualPath);
316
+ // Determine the operation type
219
317
  const operation = originalValue === undefined ? 'add' : 'update';
318
+ // Check if the operation is allowed for this path
220
319
  const isOperationAllowed = this.isOperationAllowedForPath(actualPath, operation, upstreamPaths);
221
320
  if (isOperationAllowed) {
222
321
  _.set(result, actualPath, _.cloneDeep(subAgentValue));
223
322
  }
224
323
  else {
324
+ // Only track if the sub-agent is trying to change the value
225
325
  if (!_.isEqual(subAgentValue, originalValue)) {
326
+ // Check if any operation is allowed on this path
226
327
  const isPathAllowed = this.isPathAllowed(actualPath, upstreamPaths);
227
328
  unauthorizedChanges.push({
228
329
  path: actualPath,
@@ -234,13 +335,17 @@ class PayloadManager {
234
335
  }
235
336
  }
236
337
  }
338
+ // Also check for deletions (paths in result but not in subAgentPayload)
237
339
  const resultPaths = this.getAllPaths(result);
238
340
  for (const resultPath of resultPaths) {
239
341
  const resultValue = _.get(result, resultPath);
240
342
  const subAgentValue = _.get(subAgentPayload, resultPath);
343
+ // If the path exists in result but not in subAgentPayload, it's a deletion attempt
241
344
  if (resultValue !== undefined && subAgentValue === undefined) {
345
+ // Check if delete operation is allowed
242
346
  const isDeleteAllowed = this.isOperationAllowedForPath(resultPath, 'delete', upstreamPaths);
243
347
  if (isDeleteAllowed) {
348
+ // Delete the path from result
244
349
  _.unset(result, resultPath);
245
350
  }
246
351
  else {
@@ -255,15 +360,19 @@ class PayloadManager {
255
360
  }
256
361
  }
257
362
  }
363
+ // Clean up any empty objects that resulted from property deletions in arrays
258
364
  this.cleanupEmptyArrayElements(result);
365
+ // Initialize counts
259
366
  const counts = { additions: 0, updates: 0, deletions: 0 };
260
367
  const warnings = [];
261
368
  const blockedOperations = [];
262
- const parentPayload = arguments[2];
369
+ // Count successful operations
370
+ const parentPayload = arguments[2]; // The original parent payload parameter
263
371
  for (const path of subAgentPaths) {
264
372
  const mergedValue = _.get(result, path);
265
373
  const subAgentValue = _.get(subAgentPayload, path);
266
374
  if (_.isEqual(mergedValue, subAgentValue)) {
375
+ // This change was allowed and applied
267
376
  const originalParentValue = _.get(parentPayload || {}, path);
268
377
  if (originalParentValue === undefined) {
269
378
  counts.additions++;
@@ -273,7 +382,9 @@ class PayloadManager {
273
382
  }
274
383
  }
275
384
  }
385
+ // Output consolidated warning if there were unauthorized changes
276
386
  if (unauthorizedChanges.length > 0) {
387
+ // Convert to blocked operations format
277
388
  const now = new Date().toISOString();
278
389
  for (const change of unauthorizedChanges) {
279
390
  blockedOperations.push({
@@ -286,6 +397,7 @@ class PayloadManager {
286
397
  });
287
398
  }
288
399
  warnings.push(`Sub-agent "${subAgentName}" attempted ${unauthorizedChanges.length} unauthorized operation(s)`);
400
+ // Only log to console in verbose mode
289
401
  if (verbose) {
290
402
  const groupedChanges = this.groupUnauthorizedChanges(unauthorizedChanges);
291
403
  let warningMessage = `\n⚠️ Sub-agent "${subAgentName}" attempted ${unauthorizedChanges.length} unauthorized operation${unauthorizedChanges.length > 1 ? 's' : ''}:\n`;
@@ -298,14 +410,20 @@ class PayloadManager {
298
410
  }
299
411
  }
300
412
  warningMessage += `\n ℹ️ Authorized paths: ${upstreamPaths.join(', ')}\n`;
301
- (0, core_1.LogStatus)(warningMessage);
413
+ LogStatus(warningMessage);
302
414
  }
303
415
  }
304
416
  return { counts, warnings, blockedOperations };
305
417
  }
418
+ /**
419
+ * Count changes between two payloads for tracking operations
420
+ *
421
+ * @private
422
+ */
306
423
  countChanges(original, modified, counts) {
307
424
  const originalPaths = this.getAllPaths(original || {});
308
425
  const modifiedPaths = this.getAllPaths(modified || {});
426
+ // Check for additions and updates
309
427
  for (const path of modifiedPaths) {
310
428
  const originalValue = _.get(original, path);
311
429
  const modifiedValue = _.get(modified, path);
@@ -316,15 +434,22 @@ class PayloadManager {
316
434
  counts.updates++;
317
435
  }
318
436
  }
437
+ // Check for deletions
319
438
  for (const path of originalPaths) {
320
439
  if (!modifiedPaths.includes(path)) {
321
440
  counts.deletions++;
322
441
  }
323
442
  }
324
443
  }
444
+ /**
445
+ * Groups unauthorized changes by their root path for cleaner display.
446
+ *
447
+ * @private
448
+ */
325
449
  groupUnauthorizedChanges(changes) {
326
450
  const grouped = {};
327
451
  for (const change of changes) {
452
+ // Extract the root category (first part of the path)
328
453
  const rootCategory = change.path.split('.')[0] || 'root';
329
454
  if (!grouped[rootCategory]) {
330
455
  grouped[rootCategory] = [];
@@ -333,21 +458,33 @@ class PayloadManager {
333
458
  }
334
459
  return grouped;
335
460
  }
461
+ /**
462
+ * Formats a value for display in warning messages.
463
+ *
464
+ * @private
465
+ */
336
466
  formatValue(value) {
337
467
  if (value === undefined)
338
468
  return 'undefined';
339
469
  if (value === null)
340
470
  return 'null';
341
471
  if (typeof value === 'string') {
472
+ // Clip long strings to 50 characters
342
473
  const trimmed = value.length > 50 ? value.substring(0, 50) + '...' : value;
343
474
  return `"${trimmed}"`;
344
475
  }
345
476
  if (typeof value === 'object') {
346
477
  const str = JSON.stringify(value);
478
+ // Clip long JSON to 50 characters
347
479
  return str.length > 50 ? str.substring(0, 50) + '...' : str;
348
480
  }
349
481
  return String(value);
350
482
  }
483
+ /**
484
+ * Gets all leaf paths from an object.
485
+ *
486
+ * @private
487
+ */
351
488
  getAllPaths(obj, currentPath = []) {
352
489
  const paths = [];
353
490
  if (!obj || typeof obj !== 'object') {
@@ -371,42 +508,63 @@ class PayloadManager {
371
508
  }
372
509
  return paths;
373
510
  }
511
+ /**
512
+ * Recursively cleans up empty objects from arrays after merge operations.
513
+ * This is necessary because property-level deletions can leave empty object shells in arrays.
514
+ *
515
+ * @private
516
+ */
374
517
  cleanupEmptyArrayElements(obj) {
375
518
  if (!obj || typeof obj !== 'object') {
376
519
  return;
377
520
  }
378
521
  if (Array.isArray(obj)) {
522
+ // Filter out empty objects from the array
523
+ // We need to modify the array in place to maintain references
379
524
  let writeIndex = 0;
380
525
  for (let readIndex = 0; readIndex < obj.length; readIndex++) {
381
526
  const element = obj[readIndex];
527
+ // Keep the element if it's not an empty object
528
+ // An empty object is one that is an object with no own properties
382
529
  const shouldKeep = !(element !== null &&
383
530
  typeof element === 'object' &&
384
531
  !Array.isArray(element) &&
385
532
  Object.keys(element).length === 0);
386
533
  if (shouldKeep) {
534
+ // First recurse into the element to clean up any nested arrays
387
535
  this.cleanupEmptyArrayElements(element);
536
+ // Then keep the element in the array
388
537
  if (writeIndex !== readIndex) {
389
538
  obj[writeIndex] = element;
390
539
  }
391
540
  writeIndex++;
392
541
  }
393
542
  }
543
+ // Truncate the array to remove the empty slots at the end
394
544
  obj.length = writeIndex;
395
545
  }
396
546
  else {
547
+ // For objects, recursively clean up any nested arrays
397
548
  for (const key of Object.keys(obj)) {
398
549
  this.cleanupEmptyArrayElements(obj[key]);
399
550
  }
400
551
  }
401
552
  }
553
+ /**
554
+ * Checks if a path matches any of the allowed patterns.
555
+ *
556
+ * @private
557
+ */
402
558
  isPathAllowed(actualPath, allowedPatterns) {
403
559
  const normalizedPath = actualPath.replace(/\[(\d+)\]/g, '.$1');
404
560
  for (const pattern of allowedPatterns) {
405
- const parsedPattern = (0, payload_operations_1.parsePathWithOperations)(pattern);
561
+ // Parse the pattern to extract path and operations
562
+ const parsedPattern = parsePathWithOperations(pattern);
406
563
  const pathPattern = parsedPattern.path;
407
564
  if (pathPattern === '*')
408
565
  return true;
409
566
  if (pathPattern.includes('**')) {
567
+ // Handle deep wildcards
410
568
  const regex = pathPattern
411
569
  .replace(/\./g, '\\.')
412
570
  .replace(/\*\*/g, '.*')
@@ -416,15 +574,19 @@ class PayloadManager {
416
574
  }
417
575
  }
418
576
  else {
577
+ // Handle regular patterns
578
+ // Check if pattern ends with .* which means "everything under this path"
419
579
  if (pathPattern.endsWith('.*')) {
420
- const basePath = pathPattern.slice(0, -2);
580
+ const basePath = pathPattern.slice(0, -2); // Remove the .*
421
581
  const escapedBase = basePath.replace(/\./g, '\\.');
582
+ // Match the base path followed by a dot and anything after
422
583
  const regex = `^${escapedBase}(\\..*)?$`;
423
584
  if (new RegExp(regex).test(normalizedPath)) {
424
585
  return true;
425
586
  }
426
587
  }
427
588
  else {
589
+ // Handle other wildcard patterns
428
590
  const regex = pathPattern
429
591
  .replace(/\./g, '\\.')
430
592
  .replace(/\*/g, '[^.]+')
@@ -437,9 +599,17 @@ class PayloadManager {
437
599
  }
438
600
  return false;
439
601
  }
602
+ /**
603
+ * Checks if a specific operation is allowed for a path.
604
+ *
605
+ * @param actualPath The actual path to check
606
+ * @param operation The operation to check
607
+ * @param allowedPatterns Array of allowed patterns with optional operations
608
+ * @returns True if the operation is allowed on this path
609
+ */
440
610
  isOperationAllowedForPath(actualPath, operation, allowedPatterns) {
441
611
  const normalizedPath = actualPath.replace(/\[(\d+)\]/g, '.$1');
442
- const parsedPatterns = (0, payload_operations_1.parsePathsWithOperations)(allowedPatterns);
612
+ const parsedPatterns = parsePathsWithOperations(allowedPatterns);
443
613
  for (const parsedPattern of parsedPatterns) {
444
614
  const pathPattern = parsedPattern.path;
445
615
  let pathMatches = false;
@@ -447,6 +617,7 @@ class PayloadManager {
447
617
  pathMatches = true;
448
618
  }
449
619
  else if (pathPattern.includes('**')) {
620
+ // Handle deep wildcards
450
621
  const regex = pathPattern
451
622
  .replace(/\./g, '\\.')
452
623
  .replace(/\*\*/g, '.*')
@@ -454,6 +625,7 @@ class PayloadManager {
454
625
  pathMatches = new RegExp(`^${regex}$`).test(normalizedPath);
455
626
  }
456
627
  else {
628
+ // Handle regular patterns
457
629
  if (pathPattern.endsWith('.*')) {
458
630
  const basePath = pathPattern.slice(0, -2);
459
631
  const escapedBase = basePath.replace(/\./g, '\\.');
@@ -468,13 +640,20 @@ class PayloadManager {
468
640
  pathMatches = new RegExp(`^${regex}$`).test(normalizedPath);
469
641
  }
470
642
  }
471
- if (pathMatches && (0, payload_operations_1.isOperationAllowed)(parsedPattern, operation)) {
643
+ // If path matches, check if operation is allowed
644
+ if (pathMatches && isOperationAllowed(parsedPattern, operation)) {
472
645
  return true;
473
646
  }
474
647
  }
475
648
  return false;
476
649
  }
650
+ /**
651
+ * Parses a path string into parts, handling array notation.
652
+ *
653
+ * @private
654
+ */
477
655
  parsePathParts(path) {
656
+ // Split by dots but preserve array notation
478
657
  const parts = [];
479
658
  let current = '';
480
659
  let inBracket = false;
@@ -502,6 +681,27 @@ class PayloadManager {
502
681
  }
503
682
  return parts;
504
683
  }
684
+ /**
685
+ * Deep merges two objects, with source overriding destination.
686
+ *
687
+ * This method preserves existing nested properties in the destination while
688
+ * adding or updating properties from the source. It handles nested objects
689
+ * recursively, ensuring that partial updates don't wipe out existing data.
690
+ *
691
+ * @example
692
+ * ```typescript
693
+ * const dest = { decision: { Y: 4, Z: 2 } };
694
+ * const src = { decision: { x: "string" } };
695
+ * const result = deepMerge(dest, src);
696
+ * // Returns: { decision: { x: "string", Y: 4, Z: 2 } }
697
+ * ```
698
+ *
699
+ * @param destination The target object to merge into
700
+ * @param source The source object to merge from
701
+ * @returns A new merged object with all properties from both objects
702
+ *
703
+ * @public
704
+ */
505
705
  deepMerge(destination, source) {
506
706
  if (!source)
507
707
  return destination;
@@ -511,35 +711,50 @@ class PayloadManager {
511
711
  for (const key in source) {
512
712
  if (source.hasOwnProperty(key)) {
513
713
  if (_.isObject(source[key]) && !_.isArray(source[key]) && _.isObject(result[key]) && !_.isArray(result[key])) {
714
+ // Both are objects - recursive merge
514
715
  result[key] = this.deepMerge(result[key], source[key]);
515
716
  }
516
717
  else {
718
+ // Otherwise, source overwrites destination
517
719
  result[key] = _.cloneDeep(source[key]);
518
720
  }
519
721
  }
520
722
  }
521
723
  return result;
522
724
  }
725
+ /**
726
+ * Applies an AgentPayloadChangeRequest to a payload
727
+ *
728
+ * @param originalPayload The original payload to apply changes to
729
+ * @param changeRequest The change request from the AI agent
730
+ * @param options Configuration options for the operation
731
+ * @returns Result object with the modified payload, operation counts, and any warnings
732
+ */
523
733
  applyAgentChangeRequest(originalPayload, changeRequest, options) {
524
734
  const warnings = [];
525
735
  const blockedOperations = [];
526
736
  const result = _.cloneDeep(originalPayload) || {};
527
737
  const counts = { additions: 0, updates: 0, deletions: 0 };
738
+ // Process all changes recursively
528
739
  this.processChangeRequest(result, originalPayload, changeRequest, [], counts, warnings, options?.allowedPaths, blockedOperations);
740
+ // Log if requested and in verbose mode
529
741
  if (options?.logChanges && options?.verbose) {
530
742
  this.logChangesSummary(counts, changeRequest.reasoning, options.agentName);
531
743
  }
744
+ // Analyze changes if requested
532
745
  let analysis;
533
- if (options?.analyzeChanges !== false) {
534
- const analyzer = new PayloadChangeAnalyzer_1.PayloadChangeAnalyzer();
746
+ if (options?.analyzeChanges !== false) { // Default to true
747
+ const analyzer = new PayloadChangeAnalyzer();
535
748
  analysis = analyzer.analyzeChangeRequest(originalPayload, changeRequest, result);
749
+ // Add analysis warnings to the main warnings array
536
750
  if (analysis.warnings.length > 0) {
537
751
  warnings.push(...analysis.warnings.map(w => `[${w.severity}] ${w.message}`));
538
752
  }
539
753
  }
754
+ // Generate diff if requested
540
755
  let diff;
541
- if (options?.generateDiff !== false) {
542
- const differ = new global_1.DeepDiffer();
756
+ if (options?.generateDiff !== false) { // Default to true
757
+ const differ = new DeepDiffer();
543
758
  diff = differ.diff(originalPayload, result);
544
759
  }
545
760
  return {
@@ -553,6 +768,9 @@ class PayloadManager {
553
768
  blockedOperations
554
769
  };
555
770
  }
771
+ /**
772
+ * Process a change request recursively through the payload structure
773
+ */
556
774
  processChangeRequest(target, original, changeRequest, path, counts, warnings, allowedPaths, blockedOperations) {
557
775
  if (Array.isArray(target)) {
558
776
  this.processArrayChanges(target, original, changeRequest, path, counts, warnings, allowedPaths, blockedOperations);
@@ -561,49 +779,80 @@ class PayloadManager {
561
779
  this.processObjectChanges(target, original, changeRequest, path, counts, warnings, allowedPaths, blockedOperations);
562
780
  }
563
781
  }
782
+ /**
783
+ * Process changes for array elements with support for deep merging object elements.
784
+ *
785
+ * For arrays containing objects, updates are merged deeply rather than replacing
786
+ * the entire element. This preserves existing properties while updating only the
787
+ * specified fields.
788
+ *
789
+ * Example:
790
+ * Original: [{id: 1, name: "Test", value: 100}]
791
+ * Update: [{value: 200}]
792
+ * Result: [{id: 1, name: "Test", value: 200}]
793
+ *
794
+ * Arrays of primitives still use replacement behavior for backwards compatibility.
795
+ */
564
796
  processArrayChanges(target, original, changeRequest, path, counts, warnings, allowedPaths, blockedOperations) {
565
797
  const pathStr = path.join('.');
798
+ // Get change arrays for this path
566
799
  const removeArray = pathStr ? _.get(changeRequest.removeElements, pathStr) : changeRequest.removeElements;
567
800
  const updateArray = pathStr ? _.get(changeRequest.updateElements, pathStr) : changeRequest.updateElements;
568
801
  const newArray = pathStr ? _.get(changeRequest.newElements, pathStr) : changeRequest.newElements;
802
+ // Build a new array with all changes applied
569
803
  const newTargetArray = [];
804
+ // Process existing elements
570
805
  for (let i = 0; i < target.length; i++) {
571
806
  if (removeArray && removeArray[i] === '__DELETE__') {
572
807
  counts.deletions++;
573
- continue;
808
+ continue; // Skip deleted items
574
809
  }
810
+ // Check for __DELETE__ in updateArray as well
575
811
  if (updateArray && updateArray[i] === '__DELETE__') {
576
812
  counts.deletions++;
577
- continue;
813
+ continue; // Skip deleted items
578
814
  }
815
+ // Use updated value if provided, otherwise keep original
579
816
  let elementToAdd = target[i];
580
817
  if (updateArray && this.isSignificantValue(updateArray[i])) {
818
+ // Check if both the update and current element are objects for deep merge
581
819
  if (typeof updateArray[i] === 'object' && typeof elementToAdd === 'object' &&
582
820
  !Array.isArray(updateArray[i]) && !Array.isArray(elementToAdd) &&
583
821
  updateArray[i] !== null && elementToAdd !== null) {
822
+ // Deep merge for object elements - preserve existing properties
584
823
  elementToAdd = _.cloneDeep(elementToAdd);
824
+ // Create a synthetic change request with just this update
825
+ // The updateElements should be the object itself at the root level
585
826
  const elementChangeRequest = {
586
827
  updateElements: updateArray[i]
587
828
  };
588
- this.processChangeRequest(elementToAdd, original ? original[i] : undefined, elementChangeRequest, [], counts, warnings, allowedPaths, blockedOperations);
829
+ this.processChangeRequest(elementToAdd, original ? original[i] : undefined, elementChangeRequest, [], // Empty path since updateArray[i] is already at the element level
830
+ counts, warnings, allowedPaths, blockedOperations);
589
831
  }
590
832
  else {
833
+ // For primitives or arrays, replace entirely (existing behavior)
591
834
  elementToAdd = updateArray[i];
592
835
  counts.updates++;
593
836
  }
594
837
  }
595
838
  else if (typeof elementToAdd === 'object' && elementToAdd !== null) {
839
+ // For objects/arrays that aren't being replaced, process nested changes
840
+ // IMPORTANT: Don't pass newElements for existing array elements -
841
+ // newElements items should be appended to the array, not merged into existing items
596
842
  elementToAdd = _.cloneDeep(elementToAdd);
597
843
  const changeRequestWithoutNew = {
598
844
  updateElements: changeRequest.updateElements,
599
845
  removeElements: changeRequest.removeElements,
600
846
  replaceElements: changeRequest.replaceElements,
601
847
  reasoning: changeRequest.reasoning
848
+ // Intentionally omit newElements
602
849
  };
603
850
  this.processChangeRequest(elementToAdd, original ? original[i] : undefined, changeRequestWithoutNew, [...path, i.toString()], counts, warnings, allowedPaths, blockedOperations);
604
851
  }
605
852
  newTargetArray.push(elementToAdd);
606
853
  }
854
+ // Handle items in updateArray that are beyond target.length
855
+ // These should be treated as additions (AI put new items in updateElements)
607
856
  if (updateArray && Array.isArray(updateArray)) {
608
857
  for (let i = target.length; i < updateArray.length; i++) {
609
858
  if (this.isSignificantValue(updateArray[i])) {
@@ -612,6 +861,7 @@ class PayloadManager {
612
861
  }
613
862
  }
614
863
  }
864
+ // Add new elements
615
865
  if (newArray && Array.isArray(newArray)) {
616
866
  for (const item of newArray) {
617
867
  if (this.isSignificantValue(item)) {
@@ -620,14 +870,22 @@ class PayloadManager {
620
870
  }
621
871
  }
622
872
  }
873
+ // Replace array contents in-place
623
874
  target.length = 0;
624
875
  target.push(...newTargetArray);
625
876
  }
877
+ /**
878
+ * Process changes for object properties
879
+ */
626
880
  processObjectChanges(target, original, changeRequest, path, counts, warnings, allowedPaths, blockedOperations) {
881
+ // Get all unique keys from change request
627
882
  const changeKeys = this.getChangeKeys(changeRequest, path);
883
+ // Process changes for each key
628
884
  for (const key of changeKeys) {
629
885
  this.processKeyChange(target, original, changeRequest, [...path, key], key, counts, warnings, allowedPaths, blockedOperations);
630
886
  }
887
+ // After processing all changes, check for "_DELETE_" values in updateElements
888
+ // This allows deletion within update operations at any depth
631
889
  const pathStr = path.join('.');
632
890
  const updateObj = pathStr ? _.get(changeRequest.updateElements, pathStr) : changeRequest.updateElements;
633
891
  if (updateObj && typeof updateObj === 'object' && !Array.isArray(updateObj)) {
@@ -637,21 +895,28 @@ class PayloadManager {
637
895
  keysToDelete.push(key);
638
896
  }
639
897
  }
898
+ // Delete the keys after iteration to avoid modification during iteration
640
899
  for (const key of keysToDelete) {
641
900
  delete target[key];
642
901
  counts.deletions++;
643
902
  }
644
903
  }
904
+ // Recurse into all existing keys for nested changes
645
905
  for (const key of Object.keys(target)) {
646
906
  if (!changeKeys.has(key) && typeof target[key] === 'object' && target[key] !== null) {
647
907
  this.processChangeRequest(target[key], original ? original[key] : undefined, changeRequest, [...path, key], counts, warnings, allowedPaths, blockedOperations);
648
908
  }
649
909
  }
650
910
  }
911
+ /**
912
+ * Process a single key change (add, update, or delete)
913
+ */
651
914
  processKeyChange(target, original, changeRequest, keyPath, key, counts, warnings, allowedPaths, blockedOperations) {
652
915
  const pathStr = keyPath.join('.');
916
+ // Check for replacement first (complete replacement - remove then add)
653
917
  const replaceValue = _.get(changeRequest.replaceElements, pathStr);
654
918
  if (replaceValue !== undefined) {
919
+ // Check if appropriate operation is allowed (delete if exists, add if not)
655
920
  const operation = key in target ? 'update' : 'add';
656
921
  if (allowedPaths && !this.isOperationAllowedForPath(pathStr, operation, allowedPaths)) {
657
922
  const warning = `Operation denied: Cannot replace '${pathStr}' - operation '${operation}' not allowed`;
@@ -668,6 +933,7 @@ class PayloadManager {
668
933
  }
669
934
  return;
670
935
  }
936
+ // Count the operations
671
937
  if (key in target) {
672
938
  counts.deletions++;
673
939
  counts.additions++;
@@ -675,11 +941,14 @@ class PayloadManager {
675
941
  else {
676
942
  counts.additions++;
677
943
  }
944
+ // Replace the value
678
945
  target[key] = replaceValue;
679
- return;
946
+ return; // Skip other operations since this is a complete replacement
680
947
  }
948
+ // Check for deletion
681
949
  const removeValue = _.get(changeRequest.removeElements, pathStr);
682
950
  if (removeValue === '__DELETE__') {
951
+ // Check if delete operation is allowed
683
952
  if (allowedPaths && !this.isOperationAllowedForPath(pathStr, 'delete', allowedPaths)) {
684
953
  const warning = `Operation denied: Cannot delete '${pathStr}' - operation 'delete' not allowed`;
685
954
  warnings.push(warning);
@@ -697,9 +966,13 @@ class PayloadManager {
697
966
  }
698
967
  delete target[key];
699
968
  counts.deletions++;
969
+ // Don't return here - check if there's also a new value to add
970
+ // This handles the case where AI wants to replace by removing then adding
700
971
  }
972
+ // Check for addition first
701
973
  const newValue = _.get(changeRequest.newElements, pathStr);
702
974
  if (newValue !== undefined && !(key in target)) {
975
+ // Check if add operation is allowed
703
976
  if (allowedPaths && !this.isOperationAllowedForPath(pathStr, 'add', allowedPaths)) {
704
977
  const warning = `Operation denied: Cannot add '${pathStr}' - operation 'add' not allowed`;
705
978
  warnings.push(warning);
@@ -719,7 +992,12 @@ class PayloadManager {
719
992
  counts.additions++;
720
993
  return;
721
994
  }
995
+ // Check for update
722
996
  const updateValue = _.get(changeRequest.updateElements, pathStr);
997
+ // Be forgiving: if AI put an update in newElements by mistake, treat it as an update
998
+ // EXCEPTIONS where we should NOT treat newElements as updateElements:
999
+ // 1. Both are arrays - let processArrayChanges handle append semantics
1000
+ // 2. Both are objects - recurse to find nested arrays that need append semantics
723
1001
  const bothAreArrays = Array.isArray(newValue) && Array.isArray(target[key]);
724
1002
  const bothAreObjects = newValue !== undefined &&
725
1003
  typeof newValue === 'object' && !Array.isArray(newValue) &&
@@ -734,6 +1012,7 @@ class PayloadManager {
734
1012
  shouldTreatNewAsUpdate ? newValue :
735
1013
  undefined;
736
1014
  if (effectiveUpdateValue !== undefined && key in target) {
1015
+ // Check if update operation is allowed
737
1016
  if (allowedPaths && !this.isOperationAllowedForPath(pathStr, 'update', allowedPaths)) {
738
1017
  const warning = `Operation denied: Cannot update '${pathStr}' - operation 'update' not allowed`;
739
1018
  warnings.push(warning);
@@ -749,19 +1028,25 @@ class PayloadManager {
749
1028
  }
750
1029
  return;
751
1030
  }
1031
+ // If both values are objects or arrays, we need to recursively process the update
752
1032
  if (typeof effectiveUpdateValue === 'object' && effectiveUpdateValue !== null &&
753
1033
  typeof target[key] === 'object' && target[key] !== null) {
1034
+ // Recursively process nested updates for both objects and arrays
754
1035
  this.processChangeRequest(target[key], original ? original[key] : undefined, changeRequest, keyPath, counts, warnings, allowedPaths, blockedOperations);
755
1036
  }
756
1037
  else {
1038
+ // For primitives or type mismatches, replace the value
757
1039
  target[key] = effectiveUpdateValue;
758
1040
  counts.updates++;
759
1041
  }
1042
+ // Add a soft warning if we auto-corrected the placement (but not for arrays which use append semantics)
760
1043
  if (updateValue === undefined && newValue !== undefined && shouldTreatNewAsUpdate) {
761
1044
  warnings.push(`Auto-corrected: '${key}' was in newElements but already exists (treated as update)`);
762
1045
  }
763
1046
  }
764
1047
  else if (updateValue !== undefined && !(key in target)) {
1048
+ // Be forgiving: if AI put an addition in updateElements by mistake, treat it as an addition
1049
+ // Check if add operation is allowed
765
1050
  if (allowedPaths && !this.isOperationAllowedForPath(pathStr, 'add', allowedPaths)) {
766
1051
  const warning = `Operation denied: Cannot add '${pathStr}' - operation 'add' not allowed`;
767
1052
  warnings.push(warning);
@@ -783,15 +1068,25 @@ class PayloadManager {
783
1068
  }
784
1069
  else if (removeValue !== undefined && removeValue !== '__DELETE__' &&
785
1070
  typeof target[key] === 'object' && target[key] !== null) {
1071
+ // Handle case where removeValue exists but isn't a direct deletion
1072
+ // This happens with arrays where removeValue is like [{}, '_DELETE_', {}]
1073
+ // We need to recurse to process the array removals
786
1074
  this.processChangeRequest(target[key], original ? original[key] : undefined, changeRequest, keyPath, counts, warnings, allowedPaths, blockedOperations);
787
1075
  }
788
1076
  else if (newValue !== undefined && Array.isArray(newValue) && Array.isArray(target[key])) {
1077
+ // Handle array append: newElements has an array for an existing array key
1078
+ // Recurse to let processArrayChanges handle the append
789
1079
  this.processChangeRequest(target[key], original ? original[key] : undefined, changeRequest, keyPath, counts, warnings, allowedPaths, blockedOperations);
790
1080
  }
791
1081
  else if (bothAreObjects) {
1082
+ // Handle object in newElements for existing object key
1083
+ // Recurse to find nested arrays that need append semantics
792
1084
  this.processChangeRequest(target[key], original ? original[key] : undefined, changeRequest, keyPath, counts, warnings, allowedPaths, blockedOperations);
793
1085
  }
794
1086
  }
1087
+ /**
1088
+ * Get all unique keys from change request for a given path
1089
+ */
795
1090
  getChangeKeys(changeRequest, path) {
796
1091
  const keys = new Set();
797
1092
  const pathStr = path.join('.');
@@ -810,6 +1105,9 @@ class PayloadManager {
810
1105
  addKeysFromObject(replaceObj);
811
1106
  return keys;
812
1107
  }
1108
+ /**
1109
+ * Check if a value is significant (not empty object or undefined)
1110
+ */
813
1111
  isSignificantValue(value) {
814
1112
  if (value === undefined)
815
1113
  return false;
@@ -818,15 +1116,27 @@ class PayloadManager {
818
1116
  }
819
1117
  return true;
820
1118
  }
1119
+ /**
1120
+ * Log a summary of changes applied
1121
+ */
821
1122
  logChangesSummary(counts, reasoning, agentName) {
822
1123
  const agent = agentName ? ` by ${agentName}` : '';
823
1124
  const reason = reasoning ? ` | Reason: ${reasoning}` : '';
824
- (0, core_1.LogStatus)(`Payload changes applied${agent}: ` +
1125
+ LogStatus(`Payload changes applied${agent}: ` +
825
1126
  `+${counts.additions} additions, ~${counts.updates} updates, -${counts.deletions} deletions${reason}`);
826
1127
  }
1128
+ /**
1129
+ * Apply a change request to a sub-agent payload with upstream guardrails
1130
+ *
1131
+ * This method first applies the change request, then enforces upstream path restrictions
1132
+ * to ensure the sub-agent only modifies allowed paths.
1133
+ */
827
1134
  applySubAgentChangeRequest(parentPayload, changeRequest, upstreamPaths, subAgentName) {
1135
+ // First apply the full change request
828
1136
  const changeResult = this.applyAgentChangeRequest(parentPayload, changeRequest, { validateChanges: true });
1137
+ // Then apply upstream guardrails
829
1138
  const guardedResult = this.mergeUpstreamPayload(subAgentName, parentPayload, changeResult.result, upstreamPaths);
1139
+ // Count blocked changes (this is a simplified count)
830
1140
  const blocked = this.countBlockedChanges(parentPayload, changeResult.result, guardedResult.result);
831
1141
  return {
832
1142
  ...changeResult,
@@ -835,43 +1145,98 @@ class PayloadManager {
835
1145
  blockedOperations: guardedResult.blockedOperations
836
1146
  };
837
1147
  }
1148
+ /**
1149
+ * Count how many changes were blocked by guardrails
1150
+ */
838
1151
  countBlockedChanges(_original, intended, actual) {
1152
+ // This is a simplified implementation
1153
+ // A full implementation would do deep comparison
839
1154
  const intendedStr = JSON.stringify(intended);
840
1155
  const actualStr = JSON.stringify(actual);
841
1156
  return intendedStr === actualStr ? 0 : 1;
842
1157
  }
1158
+ /**
1159
+ * Applies a payload scope transformation to extract only the scoped portion.
1160
+ *
1161
+ * @param payload The full payload to scope
1162
+ * @param scopePath The scope path (e.g., "/functionalRequirements" or "/PropA/SubProp1")
1163
+ * @returns The scoped portion of the payload
1164
+ *
1165
+ * @example
1166
+ * ```typescript
1167
+ * const payload = { functionalRequirements: { feature1: "..." }, technicalDesign: { ... } };
1168
+ * const scoped = applyPayloadScope(payload, "/functionalRequirements");
1169
+ * // Returns: { feature1: "..." }
1170
+ * ```
1171
+ */
843
1172
  applyPayloadScope(payload, scopePath) {
844
1173
  if (!payload || !scopePath)
845
1174
  return payload;
1175
+ // Remove leading slash and split path
846
1176
  const pathParts = scopePath.startsWith('/')
847
1177
  ? scopePath.slice(1).split('/')
848
1178
  : scopePath.split('/');
1179
+ // Navigate to the scoped portion
849
1180
  let current = payload;
850
1181
  for (const part of pathParts) {
851
1182
  if (current && typeof current === 'object' && current !== null && part in current) {
852
1183
  current = (current)[part];
853
1184
  }
854
1185
  else {
1186
+ // Path doesn't exist, return null
855
1187
  return null;
856
1188
  }
857
1189
  }
1190
+ // Return a deep clone of the scoped portion
858
1191
  return _.cloneDeep(current);
859
1192
  }
1193
+ /**
1194
+ * Reverses a payload scope transformation by wrapping the scoped content back into the full structure.
1195
+ *
1196
+ * @param scopedPayload The scoped payload to wrap
1197
+ * @param scopePath The scope path used for extraction
1198
+ * @returns A full payload structure with the scoped content at the correct path
1199
+ *
1200
+ * @example
1201
+ * ```typescript
1202
+ * const scoped = { feature1: "updated" };
1203
+ * const full = reversePayloadScope(scoped, "/functionalRequirements");
1204
+ * // Returns: { functionalRequirements: { feature1: "updated" } }
1205
+ * ```
1206
+ */
860
1207
  reversePayloadScope(scopedPayload, scopePath) {
861
1208
  if (!scopePath)
862
1209
  return scopedPayload;
1210
+ // Remove leading slash and split path
863
1211
  const pathParts = scopePath.startsWith('/')
864
1212
  ? scopePath.slice(1).split('/')
865
1213
  : scopePath.split('/');
1214
+ // Build the structure from the inside out
866
1215
  let result = _.cloneDeep(scopedPayload);
867
1216
  for (let i = pathParts.length - 1; i >= 0; i--) {
868
1217
  result = { [pathParts[i]]: result };
869
1218
  }
870
1219
  return result;
871
1220
  }
1221
+ /**
1222
+ * Transforms paths in a change request to account for payload scoping.
1223
+ * Prepends the scope path to all paths in the change request.
1224
+ *
1225
+ * @param changeRequest The change request with paths relative to the scoped view
1226
+ * @param scopePath The scope path to prepend
1227
+ * @returns A new change request with transformed paths
1228
+ *
1229
+ * @example
1230
+ * ```typescript
1231
+ * const request = { updateElements: { "field1": "value" } };
1232
+ * const transformed = transformChangeRequestPaths(request, "/functionalRequirements");
1233
+ * // Returns: { updateElements: { "functionalRequirements.field1": "value" } }
1234
+ * ```
1235
+ */
872
1236
  transformChangeRequestPaths(changeRequest, scopePath) {
873
1237
  if (!scopePath)
874
1238
  return changeRequest;
1239
+ // Remove leading slash and convert to dot notation
875
1240
  const pathPrefix = scopePath.startsWith('/')
876
1241
  ? scopePath.slice(1).replace(/\//g, '.')
877
1242
  : scopePath.replace(/\//g, '.');
@@ -882,10 +1247,12 @@ class PayloadManager {
882
1247
  for (const [key, value] of Object.entries(obj)) {
883
1248
  const newKey = prefix ? `${prefix}.${key}` : key;
884
1249
  if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
1250
+ // For nested objects, check if it's a leaf value
885
1251
  if (this.isLeafValue(value)) {
886
1252
  result[newKey] = value;
887
1253
  }
888
1254
  else {
1255
+ // It's a nested path structure, recurse without adding to prefix
889
1256
  Object.assign(result, transformObject(value, newKey));
890
1257
  }
891
1258
  }
@@ -903,17 +1270,23 @@ class PayloadManager {
903
1270
  reasoning: changeRequest.reasoning
904
1271
  };
905
1272
  }
1273
+ /**
1274
+ * Helper to determine if a value is a leaf value (not a path structure)
1275
+ */
906
1276
  isLeafValue(value) {
907
1277
  if (!value || typeof value !== 'object')
908
1278
  return true;
1279
+ // Check if it looks like a data value rather than a path structure
1280
+ // This is a heuristic - may need refinement based on actual use cases
909
1281
  const keys = Object.keys(value);
1282
+ // If it has common data properties, it's likely a value
910
1283
  if (keys.some(k => ['id', 'name', 'value', 'data', 'type'].includes(k.toLowerCase()))) {
911
1284
  return true;
912
1285
  }
1286
+ // If all values are primitives, it's likely a value object
913
1287
  return Object.values(value).every(v => v === null ||
914
1288
  v === undefined ||
915
1289
  typeof v !== 'object');
916
1290
  }
917
1291
  }
918
- exports.PayloadManager = PayloadManager;
919
1292
  //# sourceMappingURL=PayloadManager.js.map