@illuma-ai/agents 1.1.28 → 1.3.1

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 (272) hide show
  1. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  2. package/dist/cjs/common/spawnPath.cjs +104 -0
  3. package/dist/cjs/common/spawnPath.cjs.map +1 -0
  4. package/dist/cjs/graphs/Graph.cjs +89 -45
  5. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  6. package/dist/cjs/graphs/HandoffRegistry.cjs +47 -8
  7. package/dist/cjs/graphs/HandoffRegistry.cjs.map +1 -1
  8. package/dist/cjs/graphs/MultiAgentGraph.cjs +493 -267
  9. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  10. package/dist/cjs/graphs/phases/flushLoop.cjs +214 -0
  11. package/dist/cjs/graphs/phases/flushLoop.cjs.map +1 -0
  12. package/dist/cjs/graphs/phases/memoryFlushPhase.cjs +102 -0
  13. package/dist/cjs/graphs/phases/memoryFlushPhase.cjs.map +1 -0
  14. package/dist/cjs/llm/bedrock/index.cjs +4 -3
  15. package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
  16. package/dist/cjs/main.cjs +117 -0
  17. package/dist/cjs/main.cjs.map +1 -1
  18. package/dist/cjs/memory/citations.cjs +69 -0
  19. package/dist/cjs/memory/citations.cjs.map +1 -0
  20. package/dist/cjs/memory/compositeBackend.cjs +60 -0
  21. package/dist/cjs/memory/compositeBackend.cjs.map +1 -0
  22. package/dist/cjs/memory/constants.cjs +232 -0
  23. package/dist/cjs/memory/constants.cjs.map +1 -0
  24. package/dist/cjs/memory/embeddings.cjs +151 -0
  25. package/dist/cjs/memory/embeddings.cjs.map +1 -0
  26. package/dist/cjs/memory/factory.cjs +95 -0
  27. package/dist/cjs/memory/factory.cjs.map +1 -0
  28. package/dist/cjs/memory/migrate.cjs +81 -0
  29. package/dist/cjs/memory/migrate.cjs.map +1 -0
  30. package/dist/cjs/memory/mmr.cjs +138 -0
  31. package/dist/cjs/memory/mmr.cjs.map +1 -0
  32. package/dist/cjs/memory/paths.cjs +217 -0
  33. package/dist/cjs/memory/paths.cjs.map +1 -0
  34. package/dist/cjs/memory/pgvectorStore.cjs +225 -0
  35. package/dist/cjs/memory/pgvectorStore.cjs.map +1 -0
  36. package/dist/cjs/memory/recallTracking.cjs +98 -0
  37. package/dist/cjs/memory/recallTracking.cjs.map +1 -0
  38. package/dist/cjs/memory/schema.sql +51 -0
  39. package/dist/cjs/memory/temporalDecay.cjs +118 -0
  40. package/dist/cjs/memory/temporalDecay.cjs.map +1 -0
  41. package/dist/cjs/nodes/ApprovalGateNode.cjs +1 -1
  42. package/dist/cjs/nodes/ApprovalGateNode.cjs.map +1 -1
  43. package/dist/cjs/prompts/memoryFlushPrompt.cjs +49 -0
  44. package/dist/cjs/prompts/memoryFlushPrompt.cjs.map +1 -0
  45. package/dist/cjs/run.cjs +16 -3
  46. package/dist/cjs/run.cjs.map +1 -1
  47. package/dist/cjs/tools/AskUser.cjs +6 -1
  48. package/dist/cjs/tools/AskUser.cjs.map +1 -1
  49. package/dist/cjs/tools/BrowserTools.cjs +1 -1
  50. package/dist/cjs/tools/BrowserTools.cjs.map +1 -1
  51. package/dist/cjs/tools/ToolNode.cjs +127 -10
  52. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  53. package/dist/cjs/tools/approval/constants.cjs +2 -2
  54. package/dist/cjs/tools/approval/constants.cjs.map +1 -1
  55. package/dist/cjs/tools/memory/index.cjs +58 -0
  56. package/dist/cjs/tools/memory/index.cjs.map +1 -0
  57. package/dist/cjs/tools/memory/memoryAppendTool.cjs +69 -0
  58. package/dist/cjs/tools/memory/memoryAppendTool.cjs.map +1 -0
  59. package/dist/cjs/tools/memory/memoryGetTool.cjs +49 -0
  60. package/dist/cjs/tools/memory/memoryGetTool.cjs.map +1 -0
  61. package/dist/cjs/tools/memory/memorySearchTool.cjs +65 -0
  62. package/dist/cjs/tools/memory/memorySearchTool.cjs.map +1 -0
  63. package/dist/cjs/tools/memory/shared.cjs +106 -0
  64. package/dist/cjs/tools/memory/shared.cjs.map +1 -0
  65. package/dist/cjs/types/graph.cjs.map +1 -1
  66. package/dist/cjs/utils/childAgentContext.cjs +242 -0
  67. package/dist/cjs/utils/childAgentContext.cjs.map +1 -0
  68. package/dist/cjs/utils/errors.cjs +113 -0
  69. package/dist/cjs/utils/errors.cjs.map +1 -0
  70. package/dist/cjs/utils/events.cjs +36 -7
  71. package/dist/cjs/utils/events.cjs.map +1 -1
  72. package/dist/cjs/utils/finishReasons.cjs +44 -0
  73. package/dist/cjs/utils/finishReasons.cjs.map +1 -0
  74. package/dist/cjs/utils/llm.cjs.map +1 -1
  75. package/dist/cjs/utils/logging.cjs +34 -0
  76. package/dist/cjs/utils/logging.cjs.map +1 -0
  77. package/dist/cjs/utils/toolCallNormalization.cjs +250 -0
  78. package/dist/cjs/utils/toolCallNormalization.cjs.map +1 -0
  79. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  80. package/dist/esm/common/spawnPath.mjs +95 -0
  81. package/dist/esm/common/spawnPath.mjs.map +1 -0
  82. package/dist/esm/graphs/Graph.mjs +89 -45
  83. package/dist/esm/graphs/Graph.mjs.map +1 -1
  84. package/dist/esm/graphs/HandoffRegistry.mjs +47 -8
  85. package/dist/esm/graphs/HandoffRegistry.mjs.map +1 -1
  86. package/dist/esm/graphs/MultiAgentGraph.mjs +493 -267
  87. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  88. package/dist/esm/graphs/phases/flushLoop.mjs +209 -0
  89. package/dist/esm/graphs/phases/flushLoop.mjs.map +1 -0
  90. package/dist/esm/graphs/phases/memoryFlushPhase.mjs +99 -0
  91. package/dist/esm/graphs/phases/memoryFlushPhase.mjs.map +1 -0
  92. package/dist/esm/llm/bedrock/index.mjs +4 -3
  93. package/dist/esm/llm/bedrock/index.mjs.map +1 -1
  94. package/dist/esm/main.mjs +21 -0
  95. package/dist/esm/main.mjs.map +1 -1
  96. package/dist/esm/memory/citations.mjs +64 -0
  97. package/dist/esm/memory/citations.mjs.map +1 -0
  98. package/dist/esm/memory/compositeBackend.mjs +58 -0
  99. package/dist/esm/memory/compositeBackend.mjs.map +1 -0
  100. package/dist/esm/memory/constants.mjs +198 -0
  101. package/dist/esm/memory/constants.mjs.map +1 -0
  102. package/dist/esm/memory/embeddings.mjs +148 -0
  103. package/dist/esm/memory/embeddings.mjs.map +1 -0
  104. package/dist/esm/memory/factory.mjs +93 -0
  105. package/dist/esm/memory/factory.mjs.map +1 -0
  106. package/dist/esm/memory/migrate.mjs +78 -0
  107. package/dist/esm/memory/migrate.mjs.map +1 -0
  108. package/dist/esm/memory/mmr.mjs +130 -0
  109. package/dist/esm/memory/mmr.mjs.map +1 -0
  110. package/dist/esm/memory/paths.mjs +207 -0
  111. package/dist/esm/memory/paths.mjs.map +1 -0
  112. package/dist/esm/memory/pgvectorStore.mjs +223 -0
  113. package/dist/esm/memory/pgvectorStore.mjs.map +1 -0
  114. package/dist/esm/memory/recallTracking.mjs +94 -0
  115. package/dist/esm/memory/recallTracking.mjs.map +1 -0
  116. package/dist/esm/memory/schema.sql +51 -0
  117. package/dist/esm/memory/temporalDecay.mjs +110 -0
  118. package/dist/esm/memory/temporalDecay.mjs.map +1 -0
  119. package/dist/esm/nodes/ApprovalGateNode.mjs +1 -1
  120. package/dist/esm/nodes/ApprovalGateNode.mjs.map +1 -1
  121. package/dist/esm/prompts/memoryFlushPrompt.mjs +44 -0
  122. package/dist/esm/prompts/memoryFlushPrompt.mjs.map +1 -0
  123. package/dist/esm/run.mjs +16 -3
  124. package/dist/esm/run.mjs.map +1 -1
  125. package/dist/esm/tools/AskUser.mjs +6 -1
  126. package/dist/esm/tools/AskUser.mjs.map +1 -1
  127. package/dist/esm/tools/BrowserTools.mjs +1 -1
  128. package/dist/esm/tools/BrowserTools.mjs.map +1 -1
  129. package/dist/esm/tools/ToolNode.mjs +128 -11
  130. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  131. package/dist/esm/tools/approval/constants.mjs +2 -2
  132. package/dist/esm/tools/approval/constants.mjs.map +1 -1
  133. package/dist/esm/tools/memory/index.mjs +46 -0
  134. package/dist/esm/tools/memory/index.mjs.map +1 -0
  135. package/dist/esm/tools/memory/memoryAppendTool.mjs +67 -0
  136. package/dist/esm/tools/memory/memoryAppendTool.mjs.map +1 -0
  137. package/dist/esm/tools/memory/memoryGetTool.mjs +47 -0
  138. package/dist/esm/tools/memory/memoryGetTool.mjs.map +1 -0
  139. package/dist/esm/tools/memory/memorySearchTool.mjs +63 -0
  140. package/dist/esm/tools/memory/memorySearchTool.mjs.map +1 -0
  141. package/dist/esm/tools/memory/shared.mjs +98 -0
  142. package/dist/esm/tools/memory/shared.mjs.map +1 -0
  143. package/dist/esm/types/graph.mjs.map +1 -1
  144. package/dist/esm/utils/childAgentContext.mjs +237 -0
  145. package/dist/esm/utils/childAgentContext.mjs.map +1 -0
  146. package/dist/esm/utils/errors.mjs +109 -0
  147. package/dist/esm/utils/errors.mjs.map +1 -0
  148. package/dist/esm/utils/events.mjs +36 -8
  149. package/dist/esm/utils/events.mjs.map +1 -1
  150. package/dist/esm/utils/finishReasons.mjs +41 -0
  151. package/dist/esm/utils/finishReasons.mjs.map +1 -0
  152. package/dist/esm/utils/llm.mjs.map +1 -1
  153. package/dist/esm/utils/logging.mjs +31 -0
  154. package/dist/esm/utils/logging.mjs.map +1 -0
  155. package/dist/esm/utils/toolCallNormalization.mjs +247 -0
  156. package/dist/esm/utils/toolCallNormalization.mjs.map +1 -0
  157. package/dist/types/common/index.d.ts +1 -0
  158. package/dist/types/common/spawnPath.d.ts +59 -0
  159. package/dist/types/graphs/HandoffRegistry.d.ts +24 -7
  160. package/dist/types/graphs/MultiAgentGraph.d.ts +43 -23
  161. package/dist/types/graphs/phases/flushLoop.d.ts +106 -0
  162. package/dist/types/graphs/phases/memoryFlushPhase.d.ts +100 -0
  163. package/dist/types/index.d.ts +7 -0
  164. package/dist/types/memory/__tests__/mockBackend.d.ts +40 -0
  165. package/dist/types/memory/citations.d.ts +39 -0
  166. package/dist/types/memory/compositeBackend.d.ts +30 -0
  167. package/dist/types/memory/constants.d.ts +121 -0
  168. package/dist/types/memory/embeddings.d.ts +15 -0
  169. package/dist/types/memory/factory.d.ts +23 -0
  170. package/dist/types/memory/index.d.ts +21 -0
  171. package/dist/types/memory/migrate.d.ts +14 -0
  172. package/dist/types/memory/mmr.d.ts +50 -0
  173. package/dist/types/memory/paths.d.ts +107 -0
  174. package/dist/types/memory/pgvectorStore.d.ts +56 -0
  175. package/dist/types/memory/recallTracking.d.ts +30 -0
  176. package/dist/types/memory/temporalDecay.d.ts +53 -0
  177. package/dist/types/memory/types.d.ts +182 -0
  178. package/dist/types/prompts/memoryFlushPrompt.d.ts +54 -0
  179. package/dist/types/run.d.ts +1 -0
  180. package/dist/types/tools/AskUser.d.ts +1 -1
  181. package/dist/types/tools/BrowserTools.d.ts +2 -2
  182. package/dist/types/tools/approval/constants.d.ts +2 -2
  183. package/dist/types/tools/memory/index.d.ts +39 -0
  184. package/dist/types/tools/memory/memoryAppendTool.d.ts +27 -0
  185. package/dist/types/tools/memory/memoryGetTool.d.ts +22 -0
  186. package/dist/types/tools/memory/memorySearchTool.d.ts +22 -0
  187. package/dist/types/tools/memory/shared.d.ts +106 -0
  188. package/dist/types/types/graph.d.ts +10 -3
  189. package/dist/types/utils/childAgentContext.d.ts +99 -0
  190. package/dist/types/utils/errors.d.ts +37 -0
  191. package/dist/types/utils/events.d.ts +21 -0
  192. package/dist/types/utils/finishReasons.d.ts +32 -0
  193. package/dist/types/utils/index.d.ts +1 -0
  194. package/dist/types/utils/logging.d.ts +2 -0
  195. package/dist/types/utils/toolCallNormalization.d.ts +44 -0
  196. package/package.json +6 -4
  197. package/src/agents/AgentContext.ts +12 -4
  198. package/src/common/__tests__/enum.test.ts +4 -2
  199. package/src/common/__tests__/spawnPath.test.ts +110 -0
  200. package/src/common/index.ts +1 -0
  201. package/src/common/spawnPath.ts +101 -0
  202. package/src/graphs/Graph.ts +95 -61
  203. package/src/graphs/HandoffRegistry.ts +48 -17
  204. package/src/graphs/MultiAgentGraph.ts +588 -327
  205. package/src/graphs/__tests__/HandoffRegistry.test.ts +4 -1
  206. package/src/graphs/__tests__/multi-agent-delegate.test.ts +61 -16
  207. package/src/graphs/__tests__/multi-agent-edges.test.ts +4 -2
  208. package/src/graphs/__tests__/multi-agent-nested-subgraph.test.ts +221 -0
  209. package/src/graphs/__tests__/structured-output.integration.test.ts +212 -118
  210. package/src/graphs/contextManagement.e2e.test.ts +1 -1
  211. package/src/graphs/phases/__tests__/flushLoop.test.ts +264 -0
  212. package/src/graphs/phases/__tests__/memoryFlushPhase.test.ts +37 -0
  213. package/src/graphs/phases/__tests__/runMemoryFlush.test.ts +150 -0
  214. package/src/graphs/phases/flushLoop.ts +303 -0
  215. package/src/graphs/phases/memoryFlushPhase.ts +209 -0
  216. package/src/index.ts +30 -1
  217. package/src/llm/bedrock/index.ts +4 -5
  218. package/src/memory/__tests__/citations.test.ts +61 -0
  219. package/src/memory/__tests__/compositeBackend.test.ts +79 -0
  220. package/src/memory/__tests__/isolation.test.ts +206 -0
  221. package/src/memory/__tests__/mmr.test.ts +148 -0
  222. package/src/memory/__tests__/mockBackend.ts +161 -0
  223. package/src/memory/__tests__/paths.test.ts +168 -0
  224. package/src/memory/__tests__/recallTracking.test.ts +96 -0
  225. package/src/memory/__tests__/temporalDecay.test.ts +151 -0
  226. package/src/memory/citations.ts +80 -0
  227. package/src/memory/compositeBackend.ts +99 -0
  228. package/src/memory/constants.ts +229 -0
  229. package/src/memory/embeddings.ts +188 -0
  230. package/src/memory/factory.ts +111 -0
  231. package/src/memory/index.ts +46 -0
  232. package/src/memory/migrate.ts +116 -0
  233. package/src/memory/mmr.ts +161 -0
  234. package/src/memory/paths.ts +258 -0
  235. package/src/memory/pgvectorStore.ts +324 -0
  236. package/src/memory/recallTracking.ts +127 -0
  237. package/src/memory/schema.sql +51 -0
  238. package/src/memory/temporalDecay.ts +134 -0
  239. package/src/memory/types.ts +185 -0
  240. package/src/nodes/ApprovalGateNode.ts +4 -10
  241. package/src/nodes/__tests__/ApprovalGateNode.test.ts +11 -20
  242. package/src/prompts/memoryFlushPrompt.ts +78 -0
  243. package/src/run.ts +17 -6
  244. package/src/scripts/test-bedrock-handoff-autonomous.ts +56 -20
  245. package/src/specs/agent-handoffs-bedrock.integration.test.ts +8 -5
  246. package/src/specs/agent-handoffs.test.ts +8 -2
  247. package/src/tools/AskUser.ts +7 -2
  248. package/src/tools/BrowserTools.ts +3 -5
  249. package/src/tools/ToolNode.ts +150 -13
  250. package/src/tools/__tests__/ToolApproval.test.ts +22 -9
  251. package/src/tools/approval/__tests__/constants.test.ts +1 -1
  252. package/src/tools/approval/constants.ts +2 -2
  253. package/src/tools/memory/__tests__/memoryTools.test.ts +205 -0
  254. package/src/tools/memory/index.ts +96 -0
  255. package/src/tools/memory/memoryAppendTool.ts +101 -0
  256. package/src/tools/memory/memoryGetTool.ts +53 -0
  257. package/src/tools/memory/memorySearchTool.ts +80 -0
  258. package/src/tools/memory/shared.ts +169 -0
  259. package/src/tools/search/search.test.ts +6 -1
  260. package/src/types/graph.ts +10 -3
  261. package/src/utils/__tests__/childAgentContext.test.ts +217 -0
  262. package/src/utils/__tests__/errors.test.ts +136 -0
  263. package/src/utils/__tests__/finishReasons.test.ts +55 -0
  264. package/src/utils/__tests__/toolCallNormalization.test.ts +181 -0
  265. package/src/utils/childAgentContext.ts +259 -0
  266. package/src/utils/errors.ts +115 -0
  267. package/src/utils/events.ts +37 -7
  268. package/src/utils/finishReasons.ts +40 -0
  269. package/src/utils/index.ts +1 -0
  270. package/src/utils/llm.ts +0 -1
  271. package/src/utils/logging.ts +45 -8
  272. package/src/utils/toolCallNormalization.ts +271 -0
@@ -0,0 +1,110 @@
1
+ import {
2
+ SPAWN_PATH_SEP,
3
+ MAX_NESTING_DEPTH,
4
+ buildSpawnPath,
5
+ spawnPathDepth,
6
+ parentSpawnPath,
7
+ spawnPathParts,
8
+ leafSpawnKey,
9
+ isAncestorSpawnPath,
10
+ } from '../spawnPath';
11
+
12
+ describe('spawnPath utilities', () => {
13
+ describe('constants', () => {
14
+ it('exports a slash separator', () => {
15
+ expect(SPAWN_PATH_SEP).toBe('/');
16
+ });
17
+ it('default max nesting depth is 5', () => {
18
+ expect(MAX_NESTING_DEPTH).toBe(5);
19
+ });
20
+ });
21
+
22
+ describe('buildSpawnPath', () => {
23
+ it('returns key as-is when parent is empty/null/undefined', () => {
24
+ expect(buildSpawnPath('', 'a')).toBe('a');
25
+ expect(buildSpawnPath(null, 'a')).toBe('a');
26
+ expect(buildSpawnPath(undefined, 'a')).toBe('a');
27
+ });
28
+ it('appends key to parent with separator', () => {
29
+ expect(buildSpawnPath('a', 'b')).toBe('a/b');
30
+ expect(buildSpawnPath('a/b', 'c')).toBe('a/b/c');
31
+ });
32
+ it('throws on empty key', () => {
33
+ expect(() => buildSpawnPath('a', '')).toThrow(/empty key/);
34
+ });
35
+ });
36
+
37
+ describe('spawnPathDepth', () => {
38
+ it('root is depth 0', () => {
39
+ expect(spawnPathDepth('')).toBe(0);
40
+ expect(spawnPathDepth(null)).toBe(0);
41
+ expect(spawnPathDepth(undefined)).toBe(0);
42
+ });
43
+ it('single segment is depth 1', () => {
44
+ expect(spawnPathDepth('a')).toBe(1);
45
+ });
46
+ it('multi segment counts segments', () => {
47
+ expect(spawnPathDepth('a/b')).toBe(2);
48
+ expect(spawnPathDepth('a/b/c/d')).toBe(4);
49
+ });
50
+ it('ignores empty segments from stray separators', () => {
51
+ expect(spawnPathDepth('a//b')).toBe(2);
52
+ expect(spawnPathDepth('/a/b')).toBe(2);
53
+ });
54
+ });
55
+
56
+ describe('parentSpawnPath', () => {
57
+ it('root returns null', () => {
58
+ expect(parentSpawnPath('')).toBeNull();
59
+ expect(parentSpawnPath(null)).toBeNull();
60
+ });
61
+ it('single segment returns empty (root)', () => {
62
+ expect(parentSpawnPath('a')).toBe('');
63
+ });
64
+ it('multi segment drops last', () => {
65
+ expect(parentSpawnPath('a/b')).toBe('a');
66
+ expect(parentSpawnPath('a/b/c')).toBe('a/b');
67
+ });
68
+ });
69
+
70
+ describe('spawnPathParts', () => {
71
+ it('empty path returns empty array', () => {
72
+ expect(spawnPathParts('')).toEqual([]);
73
+ expect(spawnPathParts(null)).toEqual([]);
74
+ });
75
+ it('splits segments', () => {
76
+ expect(spawnPathParts('a/b/c')).toEqual(['a', 'b', 'c']);
77
+ });
78
+ });
79
+
80
+ describe('leafSpawnKey', () => {
81
+ it('root returns null', () => {
82
+ expect(leafSpawnKey('')).toBeNull();
83
+ });
84
+ it('returns last segment', () => {
85
+ expect(leafSpawnKey('a')).toBe('a');
86
+ expect(leafSpawnKey('a/b/c')).toBe('c');
87
+ });
88
+ });
89
+
90
+ describe('isAncestorSpawnPath', () => {
91
+ it('root is ancestor of any non-root path', () => {
92
+ expect(isAncestorSpawnPath('', 'a')).toBe(true);
93
+ expect(isAncestorSpawnPath('', 'a/b')).toBe(true);
94
+ });
95
+ it('root is not ancestor of root', () => {
96
+ expect(isAncestorSpawnPath('', '')).toBe(false);
97
+ });
98
+ it('strict ancestor detection', () => {
99
+ expect(isAncestorSpawnPath('a', 'a/b')).toBe(true);
100
+ expect(isAncestorSpawnPath('a/b', 'a/b/c')).toBe(true);
101
+ expect(isAncestorSpawnPath('a', 'a')).toBe(false);
102
+ });
103
+ it('sibling is not ancestor', () => {
104
+ expect(isAncestorSpawnPath('a/b', 'a/c')).toBe(false);
105
+ });
106
+ it('partial-prefix is not ancestor (must respect segment boundary)', () => {
107
+ expect(isAncestorSpawnPath('ab', 'abc')).toBe(false);
108
+ });
109
+ });
110
+ });
@@ -1,4 +1,5 @@
1
1
  // src/common/index.ts
2
2
  export * from './enum';
3
3
  export * from './constants';
4
+ export * from './spawnPath';
4
5
  export * from '../tools/approval/constants';
@@ -0,0 +1,101 @@
1
+ /**
2
+ * spawnPath — hierarchical invocation identity for nested multi-agent orchestration.
3
+ *
4
+ * A spawnPath is a slash-separated chain of spawnKeys from the root of the current
5
+ * agent invocation to the current spawn. The root agent has an empty spawnPath;
6
+ * each handoff/transfer/sequence that spawns a new subgraph appends a new spawnKey.
7
+ *
8
+ * Examples:
9
+ * "" → primary agent (no spawn)
10
+ * "call_abc" → first-level handoff child
11
+ * "call_abc/call_def" → grandchild (depth 2)
12
+ * "call_abc/call_def/call_ghi" → depth 3
13
+ *
14
+ * These utilities are the single source of truth for path manipulation across:
15
+ * - @illuma-ai/agents (MultiAgentGraph, HandoffRegistry, callbacks)
16
+ * - host api (initialize.js, callbacks.js, ExecutionTrace writes)
17
+ * - host client (subagent store, sidebar rendering)
18
+ *
19
+ * See docs/multi-agent-nesting-architecture.md for the full design.
20
+ */
21
+
22
+ /** Separator between spawnKeys in a spawnPath. Chosen so that the path looks
23
+ * like a filesystem/URL path, which makes it easy to read in logs and traces. */
24
+ export const SPAWN_PATH_SEP = '/';
25
+
26
+ /** Hard cap on nested multi-agent invocations. Prevents runaway recursion.
27
+ * Can be overridden at the host api layer via MULTI_AGENT_MAX_NESTING_DEPTH. */
28
+ export const MAX_NESTING_DEPTH = 5;
29
+
30
+ /**
31
+ * Append a spawnKey to a parent spawnPath.
32
+ *
33
+ * @param parent - Parent spawnPath (may be undefined/null/empty for root)
34
+ * @param key - spawnKey to append
35
+ * @returns New spawnPath string
36
+ */
37
+ export function buildSpawnPath(
38
+ parent: string | undefined | null,
39
+ key: string
40
+ ): string {
41
+ if (!key) {
42
+ throw new Error('[spawnPath] buildSpawnPath called with empty key');
43
+ }
44
+ if (parent == null || parent === '') return key;
45
+ return `${parent}${SPAWN_PATH_SEP}${key}`;
46
+ }
47
+
48
+ /**
49
+ * Compute the depth of a spawnPath.
50
+ * Root (empty) → 0; single-segment → 1; etc.
51
+ */
52
+ export function spawnPathDepth(path: string | undefined | null): number {
53
+ if (path == null || path === '') return 0;
54
+ return path.split(SPAWN_PATH_SEP).filter(Boolean).length;
55
+ }
56
+
57
+ /**
58
+ * Return the parent spawnPath, or null if the input is already root.
59
+ *
60
+ * - parentSpawnPath("a/b/c") === "a/b"
61
+ * - parentSpawnPath("a") === ""
62
+ * - parentSpawnPath("") === null
63
+ */
64
+ export function parentSpawnPath(
65
+ path: string | undefined | null
66
+ ): string | null {
67
+ if (path == null || path === '') return null;
68
+ const parts = path.split(SPAWN_PATH_SEP).filter(Boolean);
69
+ if (parts.length <= 1) return '';
70
+ return parts.slice(0, -1).join(SPAWN_PATH_SEP);
71
+ }
72
+
73
+ /** Split a spawnPath into its constituent spawnKey segments. */
74
+ export function spawnPathParts(path: string | undefined | null): string[] {
75
+ if (path == null || path === '') return [];
76
+ return path.split(SPAWN_PATH_SEP).filter(Boolean);
77
+ }
78
+
79
+ /**
80
+ * Return the last spawnKey in a spawnPath (the "current" spawn).
81
+ * Returns null for root.
82
+ */
83
+ export function leafSpawnKey(path: string | undefined | null): string | null {
84
+ const parts = spawnPathParts(path);
85
+ return parts.length === 0 ? null : parts[parts.length - 1];
86
+ }
87
+
88
+ /**
89
+ * True if `ancestor` is a strict ancestor of `descendant`. Root ("") is
90
+ * ancestor of everything except itself.
91
+ */
92
+ export function isAncestorSpawnPath(
93
+ ancestor: string | undefined | null,
94
+ descendant: string | undefined | null
95
+ ): boolean {
96
+ const a = ancestor ?? '';
97
+ const d = descendant ?? '';
98
+ if (a === d) return false;
99
+ if (a === '') return d !== '';
100
+ return d.startsWith(a + SPAWN_PATH_SEP);
101
+ }
@@ -79,6 +79,10 @@ import { getChatModelClass, manualToolStreamProviders } from '@/llm/providers';
79
79
  import { ToolNode as CustomToolNode, toolsCondition } from '@/tools/ToolNode';
80
80
  import { ChatOpenAI, AzureChatOpenAI } from '@/llm/openai';
81
81
  import { safeDispatchCustomEvent } from '@/utils/events';
82
+ import { mlog, mwarn } from '@/utils/logging';
83
+ import { normalizeMessageToolCalls } from '@/utils/toolCallNormalization';
84
+ import { isTruncationReason } from '@/utils/finishReasons';
85
+ import { isLikelyContextOverflowError } from '@/utils/errors';
82
86
  import {
83
87
  detectDocuments,
84
88
  shouldInjectMultiDocHint,
@@ -1144,10 +1148,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1144
1148
  const resolved = agentContext.resolveStructuredOutputMode();
1145
1149
  method = resolved.method;
1146
1150
  if (resolved.warnings.length > 0) {
1147
- console.warn(
1148
- '[Graph] Structured output mode warnings:',
1149
- resolved.warnings
1150
- );
1151
+ mwarn('[Graph] Structured output mode warnings:', resolved.warnings);
1151
1152
  }
1152
1153
  } else {
1153
1154
  // Legacy fallback: use the old mode-based resolution
@@ -1172,7 +1173,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1172
1173
  );
1173
1174
  preparedSchema = prepared;
1174
1175
  if (warnings.length > 0) {
1175
- console.warn('[Graph] Schema preparation warnings:', warnings);
1176
+ mwarn('[Graph] Schema preparation warnings:', warnings);
1176
1177
  }
1177
1178
  }
1178
1179
 
@@ -1264,7 +1265,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1264
1265
  ? handleErrors
1265
1266
  : `The response did not match the expected schema. Error: ${lastError.message}. Please try again with a valid response.`;
1266
1267
 
1267
- console.warn(
1268
+ mwarn(
1268
1269
  `[Graph] Structured output attempt ${attempts} failed: ${lastError.message}. Retrying...`
1269
1270
  );
1270
1271
 
@@ -1467,7 +1468,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1467
1468
  this._toolDiscoveryCache.getNewDiscoveries(messages);
1468
1469
  if (cachedDiscoveries.length > 0) {
1469
1470
  agentContext.markToolsAsDiscovered(cachedDiscoveries);
1470
- console.debug(
1471
+ mlog(
1471
1472
  `[Graph:ToolDiscovery] Cached ${cachedDiscoveries.length} new tools (total: ${this._toolDiscoveryCache.size})`
1472
1473
  );
1473
1474
  }
@@ -1498,11 +1499,11 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1498
1499
  });
1499
1500
 
1500
1501
  // DEBUG: Log which model and tools each agent uses during handoff
1501
- console.debug(
1502
+ mlog(
1502
1503
  `[createCallModel] Agent "${agentId}" invoking LLM | provider=${agentContext.provider} | ` +
1503
- `model=${(effectiveClientOptions as Record<string, unknown>)?.model ?? 'default'} | ` +
1504
- `toolsForBinding=${toolsForBinding?.length ?? 0} | ` +
1505
- `toolNames=[${(toolsForBinding ?? []).map((t) => (t as { name?: string }).name ?? 'unknown').join(', ')}]`,
1504
+ `model=${(effectiveClientOptions as Record<string, unknown>).model ?? 'default'} | ` +
1505
+ `toolsForBinding=${toolsForBinding?.length ?? 0} | ` +
1506
+ `toolNames=[${(toolsForBinding ?? []).map((t) => (t as { name?: string }).name ?? 'unknown').join(', ')}]`
1506
1507
  );
1507
1508
 
1508
1509
  if (agentContext.systemRunnable) {
@@ -1515,7 +1516,15 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1515
1516
  if (!config.signal) {
1516
1517
  config.signal = this.signal;
1517
1518
  }
1518
- this.config = config;
1519
+ // First-writer-wins: `this.config` is used ONLY as a "has a run started"
1520
+ // existence flag by the dispatch* methods (they never read its value —
1521
+ // they read the current RunnableConfig from LangChain AsyncLocalStorage).
1522
+ // Unconditionally reassigning here races across concurrent child
1523
+ // subgraph.invoke() calls under parallel multi-agent handoffs; the last
1524
+ // writer wins, and any dispatch firing between writes would historically
1525
+ // have been tagged with the wrong child's metadata. Keeping the first
1526
+ // write pinned makes this a true flag, eliminating the race.
1527
+ this.config ??= config;
1519
1528
 
1520
1529
  let messagesToUse = messages;
1521
1530
 
@@ -1619,7 +1628,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1619
1628
 
1620
1629
  if (oldMessages.length > 0) {
1621
1630
  this._summaryInFlight = true;
1622
- console.debug(
1631
+ mlog(
1623
1632
  `[Graph:ProactiveSummary] Context at ${utilization.toFixed(1)}% (threshold ${threshold}%) — summarizing ${oldMessages.length} older msgs in background`
1624
1633
  );
1625
1634
 
@@ -1628,7 +1637,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1628
1637
  .then((updated) => {
1629
1638
  if (updated != null && updated !== '') {
1630
1639
  this._cachedRunSummary = updated;
1631
- console.debug(
1640
+ mlog(
1632
1641
  `[Graph:ProactiveSummary] Background summary ready (len=${updated.length})`
1633
1642
  );
1634
1643
  }
@@ -1838,7 +1847,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1838
1847
  }
1839
1848
  agentContext.indexTokenCountMap = viewTokenMap;
1840
1849
 
1841
- console.debug(
1850
+ mlog(
1842
1851
  `[Graph:Compaction] ${messages.length}→${viewParts.length} msgs | ` +
1843
1852
  `compacted=${compactedMessages.length} window=${recentMessages.length} | ` +
1844
1853
  `summary=${summarySource} | budget=${usedTokens}/${recentBudget}` +
@@ -1862,7 +1871,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1862
1871
  if (shouldSummarize) {
1863
1872
  if (this._summaryInFlight) {
1864
1873
  this._pendingMessagesToRefine.push(...compactedMessages);
1865
- console.debug(
1874
+ mlog(
1866
1875
  `[Graph:Compaction] Summary in-flight, queued ${compactedMessages.length} msgs (pending=${this._pendingMessagesToRefine.length})`
1867
1876
  );
1868
1877
  } else {
@@ -1915,7 +1924,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1915
1924
  deduplicateSystemMessages(messagesToUse);
1916
1925
  if (removedCount > 0) {
1917
1926
  messagesToUse = dedupedMessages;
1918
- console.debug(
1927
+ mlog(
1919
1928
  `[Graph:Dedup] Removed ${removedCount} duplicate system message(s)`
1920
1929
  );
1921
1930
  }
@@ -2039,7 +2048,6 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
2039
2048
  );
2040
2049
  }
2041
2050
 
2042
-
2043
2051
  // Get model info for analytics
2044
2052
  const bedrockOpts = agentContext.clientOptions as
2045
2053
  | t.BedrockAnthropicClientOptions
@@ -2141,23 +2149,16 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
2141
2149
  config
2142
2150
  );
2143
2151
  } catch (primaryError) {
2144
- // Check if this is a "input too long" error from Bedrock/Anthropic
2145
- const errorMessage = (primaryError as Error).message.toLowerCase();
2146
- const isInputTooLongError =
2147
- errorMessage.includes('too long') ||
2148
- errorMessage.includes('input is too long') ||
2149
- errorMessage.includes('context length') ||
2150
- errorMessage.includes('maximum context') ||
2151
- errorMessage.includes('validationexception') ||
2152
- errorMessage.includes('prompt is too long');
2152
+ const errorMessage = (primaryError as Error).message;
2153
+ const isInputTooLongError = isLikelyContextOverflowError(errorMessage);
2153
2154
 
2154
2155
  // Log when we detect the error
2155
2156
  if (isInputTooLongError) {
2156
- console.warn(
2157
+ mwarn(
2157
2158
  '[Graph] Detected input too long error:',
2158
2159
  errorMessage.substring(0, 200)
2159
2160
  );
2160
- console.warn('[Graph] Checking emergency pruning conditions:', {
2161
+ mwarn('[Graph] Checking emergency pruning conditions:', {
2161
2162
  hasPruneMessages: !!agentContext.pruneMessages,
2162
2163
  hasTokenCounter: !!agentContext.tokenCounter,
2163
2164
  maxContextTokens: agentContext.maxContextTokens,
@@ -2182,7 +2183,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
2182
2183
  const reducedMaxTokens = Math.floor(
2183
2184
  agentContext.maxContextTokens! * reductionFactor
2184
2185
  );
2185
- console.warn(
2186
+ mwarn(
2186
2187
  `[Graph] Input too long. Retrying with ${reductionFactor * 100}% context (${reducedMaxTokens} tokens)...`
2187
2188
  );
2188
2189
 
@@ -2190,7 +2191,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
2190
2191
  // This is needed when messages were dynamically added without updating the token map
2191
2192
  let tokenMapForPruning = agentContext.indexTokenCountMap;
2192
2193
  if (Object.keys(tokenMapForPruning).length < messages.length) {
2193
- console.warn(
2194
+ mwarn(
2194
2195
  '[Graph] Building fresh token count map for emergency pruning...'
2195
2196
  );
2196
2197
  tokenMapForPruning = {};
@@ -2215,7 +2216,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
2215
2216
 
2216
2217
  // Skip if we can't fit any messages
2217
2218
  if (reducedMessages.length === 0) {
2218
- console.warn(
2219
+ mwarn(
2219
2220
  `[Graph] Cannot fit any messages at ${reductionFactor * 100}% reduction, trying next level...`
2220
2221
  );
2221
2222
  continue;
@@ -2291,14 +2292,11 @@ If I seem to be missing something we discussed earlier, just give me a quick rem
2291
2292
  `[Graph] ✅ Retry successful at ${reductionFactor * 100}% with ${reducedMessages.length} messages (reduced from ${finalMessages.length})`
2292
2293
  );
2293
2294
  } catch (retryError) {
2294
- const retryErrorMsg = (retryError as Error).message.toLowerCase();
2295
- const stillTooLong =
2296
- retryErrorMsg.includes('too long') ||
2297
- retryErrorMsg.includes('context length') ||
2298
- retryErrorMsg.includes('validationexception');
2295
+ const retryErrorMsg = (retryError as Error).message;
2296
+ const stillTooLong = isLikelyContextOverflowError(retryErrorMsg);
2299
2297
 
2300
2298
  if (stillTooLong && reductionFactor > 0.1) {
2301
- console.warn(
2299
+ mwarn(
2302
2300
  `[Graph] Still too long at ${reductionFactor * 100}%, trying more aggressive pruning...`
2303
2301
  );
2304
2302
  } else {
@@ -2370,6 +2368,27 @@ If I seem to be missing something we discussed earlier, just give me a quick rem
2370
2368
  * handled everything — both paths become no-ops.
2371
2369
  */
2372
2370
  const responseMessage = result.messages?.[0];
2371
+
2372
+ // Tool-call name normalization — catches LLM output that names tools
2373
+ // with wrong delimiters (outlook/operations), prefixes
2374
+ // (functions.outlook_operations), case drift, counter suffixes, or
2375
+ // empty names recoverable from the tool_call id. Mutates in place so
2376
+ // the downstream ToolNode dispatch sees the corrected names.
2377
+ if (responseMessage && agentContext.toolMap) {
2378
+ const allowedNames = new Set(Object.keys(agentContext.toolMap));
2379
+ if (allowedNames.size > 0) {
2380
+ const rewrote = normalizeMessageToolCalls(
2381
+ responseMessage,
2382
+ allowedNames
2383
+ );
2384
+ if (rewrote) {
2385
+ mlog(
2386
+ `[Graph] normalized tool_call names on agent "${agentId}" response`
2387
+ );
2388
+ }
2389
+ }
2390
+ }
2391
+
2373
2392
  const toolCalls = (responseMessage as AIMessageChunk | undefined)
2374
2393
  ?.tool_calls;
2375
2394
  const hasToolCalls = Array.isArray(toolCalls) && toolCalls.length > 0;
@@ -2485,12 +2504,22 @@ If I seem to be missing something we discussed earlier, just give me a quick rem
2485
2504
  const messageStop = meta.messageStop as
2486
2505
  | Record<string, unknown>
2487
2506
  | undefined;
2488
- this.lastFinishReason =
2507
+ const nextReason =
2489
2508
  (meta.finish_reason as string | undefined) ?? // OpenAI/Azure
2490
2509
  (meta.stop_reason as string | undefined) ?? // Anthropic direct API
2491
2510
  (meta.stopReason as string | undefined) ?? // Bedrock invoke (non-streaming)
2492
2511
  (messageStop?.stopReason as string | undefined) ?? // Bedrock streaming
2493
2512
  (meta.finishReason as string | undefined); // VertexAI/Google
2513
+
2514
+ // Sticky on truncation: a single Graph instance is reused across
2515
+ // every scoped-subgraph inner node invocation (see MultiAgentGraph
2516
+ // buildScopedSubgraph). If an earlier inner node hit max_tokens
2517
+ // but a later inner node finished cleanly, the host's continuation layer
2518
+ // would miss the truncation signal unless we preserve it. Keep the
2519
+ // truncation reason pinned so the outer caller can retry.
2520
+ if (!isTruncationReason(this.lastFinishReason)) {
2521
+ this.lastFinishReason = nextReason;
2522
+ }
2494
2523
  }
2495
2524
 
2496
2525
  this.cleanupSignalListener();
@@ -2553,7 +2582,7 @@ If I seem to be missing something we discussed earlier, just give me a quick rem
2553
2582
  '[Graph] Deferred structured output failed after successful tool use:',
2554
2583
  structuredError
2555
2584
  );
2556
- console.warn(
2585
+ mwarn(
2557
2586
  '[Graph] Falling back to unstructured response from tool-use phase'
2558
2587
  );
2559
2588
  return result;
@@ -2578,7 +2607,10 @@ If I seem to be missing something we discussed earlier, just give me a quick rem
2578
2607
  state: t.BaseGraphState,
2579
2608
  config?: RunnableConfig
2580
2609
  ): string => {
2581
- this.config = config;
2610
+ // First-writer-wins — see note in createCallModel. `this.config` is an
2611
+ // existence flag only; assigning unconditionally would race under
2612
+ // parallel child subgraph.invoke().
2613
+ this.config ??= config;
2582
2614
  return toolsCondition(state, toolNode, this.invokedToolIds);
2583
2615
  };
2584
2616
 
@@ -2623,10 +2655,16 @@ If I seem to be missing something we discussed earlier, just give me a quick rem
2623
2655
  default: () => [],
2624
2656
  }),
2625
2657
  });
2658
+ // Pass compileOptions (including the HITL checkpointer) to the OUTER
2659
+ // workflow — not just the inner agent subgraph. hasInterrupts() calls
2660
+ // getState() on the outer compiled graph; without a checkpointer here,
2661
+ // getState reports zero tasks and the HITL interrupt/resume loop breaks
2662
+ // out immediately even though interrupt() fired correctly inside the
2663
+ // agent subgraph.
2626
2664
  const workflow = new StateGraph(StateAnnotation)
2627
2665
  .addNode(this.defaultAgentId, agentNode, { ends: [END] })
2628
2666
  .addEdge(START, this.defaultAgentId)
2629
- .compile();
2667
+ .compile(this.compileOptions as unknown as never);
2630
2668
 
2631
2669
  return workflow;
2632
2670
  }
@@ -2709,7 +2747,7 @@ If I seem to be missing something we discussed earlier, just give me a quick rem
2709
2747
  }
2710
2748
  } catch (_e) {
2711
2749
  /** If we can't get agent context, that's okay - agentId remains undefined */
2712
- console.debug(
2750
+ mlog(
2713
2751
  `[dispatchRunStep] Could not resolve agentId from metadata.langgraph_node="${(metadata as Record<string, unknown>).langgraph_node}": ${(_e as Error).message}`
2714
2752
  );
2715
2753
  }
@@ -2717,11 +2755,11 @@ If I seem to be missing something we discussed earlier, just give me a quick rem
2717
2755
 
2718
2756
  this.contentData.push(runStep);
2719
2757
  this.contentIndexMap.set(stepId, runStep.index);
2720
- await safeDispatchCustomEvent(
2721
- GraphEvents.ON_RUN_STEP,
2722
- runStep,
2723
- this.config
2724
- );
2758
+ // Pass undefined so safeDispatchCustomEvent resolves the runnable config
2759
+ // from LangChain's AsyncLocalStorage. Using the shared `this.config` would
2760
+ // race across concurrent child subgraph.invoke calls under parallel
2761
+ // multi-agent handoffs and tag events with the wrong child's spawnKey.
2762
+ await safeDispatchCustomEvent(GraphEvents.ON_RUN_STEP, runStep);
2725
2763
  return stepId;
2726
2764
  }
2727
2765
 
@@ -2862,7 +2900,7 @@ If I seem to be missing something we discussed earlier, just give me a quick rem
2862
2900
  }
2863
2901
 
2864
2902
  if (!data.id) {
2865
- console.warn('No Tool ID provided for Tool Error');
2903
+ mwarn('No Tool ID provided for Tool Error');
2866
2904
  return;
2867
2905
  }
2868
2906
 
@@ -2927,11 +2965,10 @@ If I seem to be missing something we discussed earlier, just give me a quick rem
2927
2965
  id,
2928
2966
  delta,
2929
2967
  };
2930
- await safeDispatchCustomEvent(
2931
- GraphEvents.ON_RUN_STEP_DELTA,
2932
- runStepDelta,
2933
- this.config
2934
- );
2968
+ // See dispatchRunStep note: do not pass `this.config`. The implicit
2969
+ // AsyncLocalStorage config is the correct per-async-branch source under
2970
+ // parallel handoffs.
2971
+ await safeDispatchCustomEvent(GraphEvents.ON_RUN_STEP_DELTA, runStepDelta);
2935
2972
  }
2936
2973
 
2937
2974
  async dispatchMessageDelta(id: string, delta: t.MessageDelta): Promise<void> {
@@ -2942,11 +2979,8 @@ If I seem to be missing something we discussed earlier, just give me a quick rem
2942
2979
  id,
2943
2980
  delta,
2944
2981
  };
2945
- await safeDispatchCustomEvent(
2946
- GraphEvents.ON_MESSAGE_DELTA,
2947
- messageDelta,
2948
- this.config
2949
- );
2982
+ // See dispatchRunStep note.
2983
+ await safeDispatchCustomEvent(GraphEvents.ON_MESSAGE_DELTA, messageDelta);
2950
2984
  }
2951
2985
 
2952
2986
  dispatchReasoningDelta = async (
@@ -2960,10 +2994,10 @@ If I seem to be missing something we discussed earlier, just give me a quick rem
2960
2994
  id: stepId,
2961
2995
  delta,
2962
2996
  };
2997
+ // See dispatchRunStep note.
2963
2998
  await safeDispatchCustomEvent(
2964
2999
  GraphEvents.ON_REASONING_DELTA,
2965
- reasoningDelta,
2966
- this.config
3000
+ reasoningDelta
2967
3001
  );
2968
3002
  };
2969
3003
  }