@hashgraphonline/conversational-agent 0.1.214 → 0.1.217

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 (170) hide show
  1. package/cli/dist/CLIApp.d.ts +9 -0
  2. package/cli/dist/CLIApp.js +127 -0
  3. package/cli/dist/LocalConversationalAgent.d.ts +37 -0
  4. package/cli/dist/LocalConversationalAgent.js +58 -0
  5. package/cli/dist/app.d.ts +16 -0
  6. package/cli/dist/app.js +13 -0
  7. package/cli/dist/cli.d.ts +2 -0
  8. package/cli/dist/cli.js +51 -0
  9. package/cli/dist/components/AppContainer.d.ts +16 -0
  10. package/cli/dist/components/AppContainer.js +24 -0
  11. package/cli/dist/components/AppScreens.d.ts +2 -0
  12. package/cli/dist/components/AppScreens.js +259 -0
  13. package/cli/dist/components/ChatScreen.d.ts +15 -0
  14. package/cli/dist/components/ChatScreen.js +39 -0
  15. package/cli/dist/components/DebugLoadingScreen.d.ts +5 -0
  16. package/cli/dist/components/DebugLoadingScreen.js +31 -0
  17. package/cli/dist/components/LoadingScreen.d.ts +2 -0
  18. package/cli/dist/components/LoadingScreen.js +16 -0
  19. package/cli/dist/components/LoadingScreenDebug.d.ts +5 -0
  20. package/cli/dist/components/LoadingScreenDebug.js +27 -0
  21. package/cli/dist/components/MCPConfigScreen.d.ts +28 -0
  22. package/cli/dist/components/MCPConfigScreen.js +168 -0
  23. package/cli/dist/components/ScreenRouter.d.ts +12 -0
  24. package/cli/dist/components/ScreenRouter.js +22 -0
  25. package/cli/dist/components/SetupScreen.d.ts +15 -0
  26. package/cli/dist/components/SetupScreen.js +65 -0
  27. package/cli/dist/components/SingleLoadingScreen.d.ts +5 -0
  28. package/cli/dist/components/SingleLoadingScreen.js +27 -0
  29. package/cli/dist/components/StatusBadge.d.ts +7 -0
  30. package/cli/dist/components/StatusBadge.js +28 -0
  31. package/cli/dist/components/TerminalWindow.d.ts +8 -0
  32. package/cli/dist/components/TerminalWindow.js +24 -0
  33. package/cli/dist/components/WelcomeScreen.d.ts +11 -0
  34. package/cli/dist/components/WelcomeScreen.js +47 -0
  35. package/cli/dist/context/AppContext.d.ts +68 -0
  36. package/cli/dist/context/AppContext.js +363 -0
  37. package/cli/dist/hooks/useInitializeAgent.d.ts +19 -0
  38. package/cli/dist/hooks/useInitializeAgent.js +28 -0
  39. package/cli/dist/hooks/useStableState.d.ts +38 -0
  40. package/cli/dist/hooks/useStableState.js +68 -0
  41. package/cli/dist/managers/AgentManager.d.ts +57 -0
  42. package/cli/dist/managers/AgentManager.js +119 -0
  43. package/cli/dist/managers/ConfigManager.d.ts +53 -0
  44. package/cli/dist/managers/ConfigManager.js +173 -0
  45. package/cli/dist/types.d.ts +31 -0
  46. package/cli/dist/types.js +19 -0
  47. package/dist/cjs/base-agent.d.ts +2 -0
  48. package/dist/cjs/conversational-agent.d.ts +8 -0
  49. package/dist/cjs/core/ToolRegistry.d.ts +130 -0
  50. package/dist/cjs/execution/ExecutionPipeline.d.ts +81 -0
  51. package/dist/cjs/forms/FormEngine.d.ts +121 -0
  52. package/dist/cjs/forms/field-type-registry.d.ts +51 -0
  53. package/dist/cjs/forms/form-generator.d.ts +123 -0
  54. package/dist/cjs/forms/index.d.ts +2 -0
  55. package/dist/cjs/forms/types.d.ts +108 -0
  56. package/dist/cjs/index.cjs +1 -1
  57. package/dist/cjs/index.cjs.map +1 -1
  58. package/dist/cjs/index.d.ts +5 -0
  59. package/dist/cjs/langchain/FormAwareAgentExecutor.d.ts +108 -0
  60. package/dist/cjs/langchain/FormValidatingToolWrapper.d.ts +81 -0
  61. package/dist/cjs/langchain-agent.d.ts +65 -0
  62. package/dist/cjs/memory/ContentStorage.d.ts +7 -0
  63. package/dist/cjs/memory/SmartMemoryManager.d.ts +1 -0
  64. package/dist/cjs/services/ContentStoreManager.d.ts +11 -1
  65. package/dist/cjs/utils/ResponseFormatter.d.ts +26 -0
  66. package/dist/esm/index.js +8 -1
  67. package/dist/esm/index.js.map +1 -1
  68. package/dist/esm/index12.js +1 -1
  69. package/dist/esm/index12.js.map +1 -1
  70. package/dist/esm/index14.js +23 -5
  71. package/dist/esm/index14.js.map +1 -1
  72. package/dist/esm/index15.js +25 -4
  73. package/dist/esm/index15.js.map +1 -1
  74. package/dist/esm/index16.js +4 -2
  75. package/dist/esm/index16.js.map +1 -1
  76. package/dist/esm/index17.js +2 -7
  77. package/dist/esm/index17.js.map +1 -1
  78. package/dist/esm/index18.js +609 -36
  79. package/dist/esm/index18.js.map +1 -1
  80. package/dist/esm/index19.js +229 -84
  81. package/dist/esm/index19.js.map +1 -1
  82. package/dist/esm/index20.js +111 -17
  83. package/dist/esm/index20.js.map +1 -1
  84. package/dist/esm/index21.js +44 -7
  85. package/dist/esm/index21.js.map +1 -1
  86. package/dist/esm/index22.js +86 -157
  87. package/dist/esm/index22.js.map +1 -1
  88. package/dist/esm/index23.js +32 -150
  89. package/dist/esm/index23.js.map +1 -1
  90. package/dist/esm/index24.js +746 -80
  91. package/dist/esm/index24.js.map +1 -1
  92. package/dist/esm/index25.js +154 -45
  93. package/dist/esm/index25.js.map +1 -1
  94. package/dist/esm/index26.js +149 -24
  95. package/dist/esm/index26.js.map +1 -1
  96. package/dist/esm/index27.js +196 -217
  97. package/dist/esm/index27.js.map +1 -1
  98. package/dist/esm/index28.js +187 -0
  99. package/dist/esm/index28.js.map +1 -0
  100. package/dist/esm/index29.js +308 -0
  101. package/dist/esm/index29.js.map +1 -0
  102. package/dist/esm/index30.js +159 -0
  103. package/dist/esm/index30.js.map +1 -0
  104. package/dist/esm/index31.js +68 -0
  105. package/dist/esm/index31.js.map +1 -0
  106. package/dist/esm/index32.js +30 -0
  107. package/dist/esm/index32.js.map +1 -0
  108. package/dist/esm/index33.js +95 -0
  109. package/dist/esm/index33.js.map +1 -0
  110. package/dist/esm/index34.js +245 -0
  111. package/dist/esm/index34.js.map +1 -0
  112. package/dist/esm/index5.js +2 -2
  113. package/dist/esm/index5.js.map +1 -1
  114. package/dist/esm/index6.js +68 -25
  115. package/dist/esm/index6.js.map +1 -1
  116. package/dist/esm/index7.js.map +1 -1
  117. package/dist/esm/index8.js +744 -70
  118. package/dist/esm/index8.js.map +1 -1
  119. package/dist/types/base-agent.d.ts +2 -0
  120. package/dist/types/conversational-agent.d.ts +8 -0
  121. package/dist/types/core/ToolRegistry.d.ts +130 -0
  122. package/dist/types/execution/ExecutionPipeline.d.ts +81 -0
  123. package/dist/types/forms/FormEngine.d.ts +121 -0
  124. package/dist/types/forms/field-type-registry.d.ts +51 -0
  125. package/dist/types/forms/form-generator.d.ts +123 -0
  126. package/dist/types/forms/index.d.ts +2 -0
  127. package/dist/types/forms/types.d.ts +108 -0
  128. package/dist/types/index.d.ts +5 -0
  129. package/dist/types/langchain/FormAwareAgentExecutor.d.ts +108 -0
  130. package/dist/types/langchain/FormValidatingToolWrapper.d.ts +81 -0
  131. package/dist/types/langchain-agent.d.ts +65 -0
  132. package/dist/types/memory/ContentStorage.d.ts +7 -0
  133. package/dist/types/memory/SmartMemoryManager.d.ts +1 -0
  134. package/dist/types/services/ContentStoreManager.d.ts +11 -1
  135. package/dist/types/utils/ResponseFormatter.d.ts +26 -0
  136. package/package.json +35 -34
  137. package/src/base-agent.ts +2 -0
  138. package/src/config/system-message.ts +14 -0
  139. package/src/context/ReferenceContextManager.ts +1 -1
  140. package/src/conversational-agent.ts +95 -38
  141. package/src/core/ToolRegistry.ts +358 -0
  142. package/src/execution/ExecutionPipeline.ts +301 -0
  143. package/src/forms/FormEngine.ts +443 -0
  144. package/src/forms/field-type-registry.ts +203 -0
  145. package/src/forms/form-generator.ts +841 -0
  146. package/src/forms/index.ts +2 -0
  147. package/src/forms/types.ts +125 -0
  148. package/src/index.ts +9 -0
  149. package/src/langchain/FormAwareAgentExecutor.ts +971 -0
  150. package/src/langchain/FormValidatingToolWrapper.ts +355 -0
  151. package/src/langchain-agent.ts +1034 -87
  152. package/src/mcp/ContentProcessor.ts +20 -4
  153. package/src/mcp/MCPClientManager.ts +1 -1
  154. package/src/mcp/adapters/langchain.ts +1 -1
  155. package/src/memory/ContentStorage.ts +25 -5
  156. package/src/memory/SmartMemoryManager.ts +27 -4
  157. package/src/memory/TokenCounter.ts +1 -1
  158. package/src/plugins/hbar/HbarPlugin.ts +0 -1
  159. package/src/scripts/test-external-tool-wrapper.ts +103 -0
  160. package/src/scripts/test-hedera-kit-wrapper.ts +265 -0
  161. package/src/scripts/test-inscribe-form-generation.ts +494 -0
  162. package/src/scripts/test-inscribe-wrapper-verification.ts +220 -0
  163. package/src/services/ContentStoreManager.ts +23 -9
  164. package/src/services/EntityResolver.ts +2 -9
  165. package/src/tools/EntityResolverTool.ts +5 -8
  166. package/src/utils/ResponseFormatter.ts +146 -0
  167. package/cli/readme.md +0 -181
  168. package/dist/cjs/langchain/ContentAwareAgentExecutor.d.ts +0 -14
  169. package/dist/types/langchain/ContentAwareAgentExecutor.d.ts +0 -14
  170. package/src/langchain/ContentAwareAgentExecutor.ts +0 -19
@@ -1,7 +1,7 @@
1
1
  import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
2
2
  import type { StructuredTool } from '@langchain/core/tools';
3
3
  import { createOpenAIToolsAgent } from 'langchain/agents';
4
- import { ContentAwareAgentExecutor } from './langchain/ContentAwareAgentExecutor';
4
+ import { FormAwareAgentExecutor } from './langchain/FormAwareAgentExecutor';
5
5
  import {
6
6
  ChatPromptTemplate,
7
7
  MessagesPlaceholder,
@@ -24,16 +24,559 @@ import {
24
24
  import { MCPClientManager } from './mcp/MCPClientManager';
25
25
  import { convertMCPToolToLangChain } from './mcp/adapters/langchain';
26
26
  import { SmartMemoryManager } from './memory/SmartMemoryManager';
27
- import type { MCPConnectionStatus } from './mcp/types';
27
+ import type { MCPConnectionStatus, MCPServerConfig } from './mcp/types';
28
+ import { ResponseFormatter } from './utils/ResponseFormatter';
29
+ import type { FormSubmission } from './forms/types';
30
+ import type { ToolRegistrationOptions } from './core/ToolRegistry';
31
+ import { HumanMessage, AIMessage } from '@langchain/core/messages';
32
+ import { ToolRegistry } from './core/ToolRegistry';
33
+ import {
34
+ ExecutionPipeline,
35
+ SessionContext,
36
+ } from './execution/ExecutionPipeline';
37
+ import { FormEngine } from './forms/FormEngine';
38
+ import type { ChainValues } from '@langchain/core/utils/types';
39
+
40
+ interface RenderConfigSchema {
41
+ _renderConfig?: Record<string, unknown>;
42
+ }
43
+
44
+ interface ToolExecutionData {
45
+ type: string;
46
+ formId?: string;
47
+ parameters?: Record<string, unknown>;
48
+ }
49
+
50
+ interface IntermediateStep {
51
+ action?: {
52
+ tool?: string;
53
+ toolInput?: Record<string, unknown>;
54
+ };
55
+ observation?: unknown;
56
+ }
57
+ interface RenderConfigSchema {
58
+ _renderConfig?: Record<string, unknown>;
59
+ }
60
+
61
+ interface ToolExecutionData {
62
+ type: string;
63
+ formId?: string;
64
+ parameters?: Record<string, unknown>;
65
+ }
66
+
67
+ interface IntermediateStep {
68
+ action?: {
69
+ tool?: string;
70
+ toolInput?: Record<string, unknown>;
71
+ };
72
+ observation?: unknown;
73
+ }
28
74
 
29
75
  export class LangChainAgent extends BaseAgent {
30
- private executor: ContentAwareAgentExecutor | undefined;
76
+ private executor: FormAwareAgentExecutor | undefined;
31
77
  private systemMessage = '';
32
78
  private mcpManager?: MCPClientManager;
33
79
  private smartMemory: SmartMemoryManager | undefined;
34
80
  private mcpConnectionStatus: Map<string, MCPConnectionStatus> = new Map();
81
+ private toolRegistry!: ToolRegistry;
82
+ private executionPipeline!: ExecutionPipeline;
83
+ private formEngine!: FormEngine;
84
+
85
+ /**
86
+ * Get inscription tool by capability instead of hardcoded name
87
+ */
88
+ private getInscriptionTool(): StructuredTool | null {
89
+ const criticalTools = this.toolRegistry.getToolsByCapability(
90
+ 'priority',
91
+ 'critical'
92
+ );
93
+
94
+ for (const entry of criticalTools) {
95
+ const tool = entry.tool;
96
+ const name = tool.name.toLowerCase();
97
+ const desc = tool.description?.toLowerCase() || '';
98
+
99
+ if (
100
+ name.includes('inscribe') ||
101
+ name.includes('hashinal') ||
102
+ desc.includes('inscribe') ||
103
+ desc.includes('hashinal')
104
+ ) {
105
+ return tool;
106
+ }
107
+ }
108
+
109
+ const allTools = this.toolRegistry.getAllRegistryEntries();
110
+ for (const entry of allTools) {
111
+ const tool = entry.tool;
112
+ const name = tool.name.toLowerCase();
113
+ const desc = tool.description?.toLowerCase() || '';
114
+
115
+ if (
116
+ name.includes('inscribe') ||
117
+ name.includes('hashinal') ||
118
+ desc.includes('inscribe') ||
119
+ desc.includes('hashinal')
120
+ ) {
121
+ return tool;
122
+ }
123
+ }
124
+
125
+ return null;
126
+ }
127
+
128
+ /**
129
+ * Get a tool by name from the registry with type safety
130
+ */
131
+ private getTool(name: string): StructuredTool | null {
132
+ const entry = this.toolRegistry.getTool(name);
133
+ return entry ? entry.tool : null;
134
+ }
135
+
136
+ /**
137
+ * Execute a tool directly with parameters, optionally using ExecutionPipeline
138
+ */
139
+ private async executeToolDirect(
140
+ toolName: string,
141
+ parameters: Record<string, unknown>,
142
+ useExecutionPipeline = false
143
+ ): Promise<string> {
144
+ if (useExecutionPipeline && this.executionPipeline && this.smartMemory) {
145
+ const sessionContext: SessionContext = {
146
+ sessionId: `session-${Date.now()}`,
147
+ timestamp: Date.now(),
148
+ };
149
+
150
+ const result = await this.executionPipeline.execute(
151
+ toolName,
152
+ parameters,
153
+ sessionContext
154
+ );
155
+
156
+ if (!result.success) {
157
+ throw new Error(result.error || 'Pipeline execution failed');
158
+ }
159
+
160
+ return result.output;
161
+ }
162
+
163
+ const entry = this.toolRegistry.getTool(toolName);
164
+ if (!entry) {
165
+ throw new Error(`Tool not found: ${toolName}`);
166
+ }
167
+
168
+ const mergedArgs = { ...parameters, renderForm: false };
169
+
170
+ if (entry.wrapper) {
171
+ const maybeWrapper = entry.tool as unknown as {
172
+ originalTool?: {
173
+ call?: (a: Record<string, unknown>) => Promise<string>;
174
+ };
175
+ };
176
+ if (maybeWrapper.originalTool?.call) {
177
+ return await maybeWrapper.originalTool.call(mergedArgs);
178
+ }
179
+ }
180
+
181
+ return await entry.tool.call(mergedArgs);
182
+ }
183
+
184
+ /**
185
+ * Create a standard ChatResponse from tool output
186
+ */
187
+ private createToolResponse(toolOutput: string): ChatResponse {
188
+ return {
189
+ output: toolOutput,
190
+ message: toolOutput,
191
+ notes: [],
192
+ };
193
+ }
194
+
195
+ /**
196
+ * Handle TOOL_EXECUTION format messages
197
+ */
198
+ private async handleToolExecution(
199
+ message: string,
200
+ context?: ConversationContext
201
+ ): Promise<ChatResponse | null> {
202
+ let isToolExecution = false;
203
+ let toolExecutionData: ToolExecutionData | null = null;
204
+
205
+ try {
206
+ if (message.includes('TOOL_EXECUTION')) {
207
+ const parsed = JSON.parse(message);
208
+ if (parsed.type === 'TOOL_EXECUTION') {
209
+ isToolExecution = true;
210
+ toolExecutionData = parsed;
211
+ }
212
+ }
213
+ } catch {}
214
+
215
+ if (!isToolExecution || !toolExecutionData?.formId) {
216
+ return null;
217
+ }
218
+
219
+ try {
220
+ const params = (toolExecutionData.parameters || {}) as Record<
221
+ string,
222
+ unknown
223
+ >;
224
+ const toolName = (toolExecutionData as unknown as { toolName?: string })
225
+ .toolName;
226
+
227
+ if (toolName) {
228
+ const toolOutput = await this.executeToolDirect(toolName, params);
229
+ return this.createToolResponse(toolOutput);
230
+ }
231
+ } catch {}
232
+
233
+ const formSubmission: FormSubmission = {
234
+ formId: toolExecutionData.formId,
235
+ toolName:
236
+ (toolExecutionData as unknown as { toolName?: string }).toolName || '',
237
+ parameters: toolExecutionData.parameters || {},
238
+ timestamp: Date.now(),
239
+ };
240
+
241
+ if (
242
+ this.executor &&
243
+ 'processFormSubmission' in this.executor &&
244
+ typeof this.executor.processFormSubmission === 'function'
245
+ ) {
246
+ return this.processFormSubmission(formSubmission, context);
247
+ }
248
+
249
+ return null;
250
+ }
251
+
252
+ /**
253
+ * Handle direct tool execution commands
254
+ */
255
+ private async handleDirectToolExecution(
256
+ message: string
257
+ ): Promise<ChatResponse | null> {
258
+ if (
259
+ typeof message !== 'string' ||
260
+ !message.includes('Please execute the following tool:')
261
+ ) {
262
+ return null;
263
+ }
264
+
265
+ try {
266
+ const toolLineMatch = message.match(/Tool:\s*(.+)/);
267
+ const argsLineIndex = message.indexOf('Arguments:');
268
+
269
+ if (toolLineMatch && argsLineIndex !== -1) {
270
+ const toolName = toolLineMatch[1].trim();
271
+ const argsText = message
272
+ .slice(argsLineIndex + 'Arguments:'.length)
273
+ .trim();
274
+
275
+ let args: Record<string, unknown> = {};
276
+ try {
277
+ args = JSON.parse(argsText);
278
+ } catch {}
279
+
280
+ const toolOutput = await this.executeToolDirect(toolName, args);
281
+ return this.createToolResponse(toolOutput);
282
+ }
283
+ } catch {}
284
+
285
+ return null;
286
+ }
287
+
288
+ /**
289
+ * Handle JSON format tool calls and form submissions
290
+ */
291
+ private async handleJsonToolCalls(
292
+ message: string,
293
+ context?: ConversationContext
294
+ ): Promise<ChatResponse | null> {
295
+ if (typeof message !== 'string') {
296
+ return null;
297
+ }
298
+
299
+ try {
300
+ const trimmed = message.trim();
301
+ if (
302
+ !(trimmed.startsWith('{') && trimmed.endsWith('}')) &&
303
+ !(trimmed.startsWith('[') && trimmed.endsWith(']'))
304
+ ) {
305
+ return null;
306
+ }
307
+
308
+ const obj = JSON.parse(trimmed) as Record<string, unknown>;
309
+ const formId = obj['formId'] as string | undefined;
310
+ const toolName = (obj['toolName'] as string) || '';
311
+ const parameters = (obj['parameters'] as Record<string, unknown>) || {};
312
+
313
+ if (
314
+ formId &&
315
+ this.executor &&
316
+ 'processFormSubmission' in this.executor &&
317
+ typeof this.executor.processFormSubmission === 'function'
318
+ ) {
319
+ return this.processFormSubmission(
320
+ { formId, toolName, parameters, timestamp: Date.now() },
321
+ context
322
+ );
323
+ }
324
+
325
+ if (toolName) {
326
+ const toolOutput = await this.executeToolDirect(toolName, parameters);
327
+ return this.createToolResponse(toolOutput);
328
+ }
329
+ } catch {}
330
+
331
+ return null;
332
+ }
333
+
334
+ /**
335
+ * Handle content-ref messages for inscription tools
336
+ */
337
+ private async handleContentRefMessages(
338
+ message: string
339
+ ): Promise<ChatResponse | null> {
340
+ if (typeof message !== 'string' || !message.includes('content-ref:')) {
341
+ return null;
342
+ }
343
+
344
+ try {
345
+ const tool = this.getInscriptionTool();
346
+ if (!tool) {
347
+ return null;
348
+ }
349
+
350
+ const idMatch =
351
+ message.match(/content-ref:([A-Za-z0-9_\-]+)/i) ||
352
+ message.match(/content-ref:([^\s)]+)/i);
353
+ const contentRef =
354
+ idMatch && idMatch[1]
355
+ ? `content-ref:${idMatch[1]}`
356
+ : message.match(/content-ref:[^\s)]+/i)?.[0] || undefined;
357
+
358
+ const args = contentRef
359
+ ? ({ contentRef, renderForm: true, withHashLinkBlocks: true } as Record<string, unknown>)
360
+ : ({ renderForm: true, withHashLinkBlocks: true } as Record<string, unknown>);
361
+
362
+ const toolOutput = await tool.call(args);
363
+ let parsed: Record<string, unknown> | undefined;
364
+
365
+ try {
366
+ parsed =
367
+ typeof toolOutput === 'string'
368
+ ? (JSON.parse(toolOutput) as Record<string, unknown>)
369
+ : (toolOutput as unknown as Record<string, unknown>);
370
+ } catch {}
371
+
372
+ if (parsed && parsed['requiresForm'] && parsed['formMessage']) {
373
+ const pending = new Map<
374
+ string,
375
+ {
376
+ toolName: string;
377
+ originalInput: Record<string, unknown>;
378
+ originalToolInput?: Record<string, unknown>;
379
+ schema: unknown;
380
+ }
381
+ >();
382
+
383
+ const originalInput = {
384
+ input: message,
385
+ chat_history: this.smartMemory!.getMessages(),
386
+ } as Record<string, unknown>;
387
+
388
+ const formMessage = parsed['formMessage'] as { id: string };
389
+ pending.set(formMessage.id, {
390
+ toolName: tool.name,
391
+ originalInput,
392
+ originalToolInput: args,
393
+ schema: null,
394
+ });
395
+
396
+ const maybeRestore = this.executor as unknown as {
397
+ restorePendingForms?: (p: Map<string, unknown>) => void;
398
+ };
399
+
400
+ if (typeof maybeRestore.restorePendingForms === 'function') {
401
+ (
402
+ maybeRestore.restorePendingForms as unknown as (
403
+ p: Map<string, unknown>
404
+ ) => void
405
+ )(pending as unknown as Map<string, unknown>);
406
+ }
407
+
408
+ const outputMsg =
409
+ (parsed['message'] as string) ||
410
+ 'Please complete the form to continue.';
411
+
412
+ return {
413
+ output: outputMsg,
414
+ message: outputMsg,
415
+ notes: [],
416
+ requiresForm: true,
417
+ formMessage: formMessage as unknown as ChatResponse['formMessage'],
418
+ } as ChatResponse;
419
+ }
420
+ } catch {}
421
+
422
+ return null;
423
+ }
424
+
425
+ /**
426
+ * Process executor result and format response
427
+ */
428
+ private async processExecutorResult(
429
+ result: ChainValues
430
+ ): Promise<ChatResponse> {
431
+ let outputStr = '';
432
+ if (typeof result.output === 'string') {
433
+ outputStr = result.output;
434
+ } else if (result.output) {
435
+ try {
436
+ outputStr = JSON.stringify(result.output);
437
+ } catch {
438
+ outputStr = String(result.output);
439
+ }
440
+ }
441
+
442
+ let response: ChatResponse = {
443
+ output: outputStr,
444
+ message: outputStr,
445
+ notes: [],
446
+ intermediateSteps: result.intermediateSteps,
447
+ };
448
+
449
+ if (result.requiresForm && result.formMessage) {
450
+ response.formMessage = result.formMessage;
451
+ response.requiresForm = true;
452
+ }
453
+
454
+ if (result.intermediateSteps && Array.isArray(result.intermediateSteps)) {
455
+ const toolCalls = result.intermediateSteps.map(
456
+ (step: IntermediateStep, index: number) => ({
457
+ id: `call_${index}`,
458
+ name: step.action?.tool || 'unknown',
459
+ args: step.action?.toolInput || {},
460
+ output:
461
+ typeof step.observation === 'string'
462
+ ? step.observation
463
+ : JSON.stringify(step.observation),
464
+ })
465
+ );
466
+
467
+ if (toolCalls.length > 0) {
468
+ response.tool_calls = toolCalls;
469
+ }
470
+ }
471
+
472
+ const parsedSteps = result?.intermediateSteps?.[0]?.observation;
473
+ if (
474
+ parsedSteps &&
475
+ typeof parsedSteps === 'string' &&
476
+ this.isJSON(parsedSteps)
477
+ ) {
478
+ try {
479
+ const parsed = JSON.parse(parsedSteps);
480
+
481
+ if (ResponseFormatter.isInscriptionResponse(parsed)) {
482
+ const formattedMessage =
483
+ ResponseFormatter.formatInscriptionResponse(parsed);
484
+ response.output = formattedMessage;
485
+ response.message = formattedMessage;
486
+
487
+ if (parsed.inscription) {
488
+ response.inscription = parsed.inscription;
489
+ }
490
+ if (parsed.metadata) {
491
+ response.metadata = {
492
+ ...response.metadata,
493
+ ...parsed.metadata,
494
+ };
495
+ }
496
+ } else {
497
+ response = { ...response, ...parsed };
498
+ }
499
+
500
+ const blockMetadata = this.processHashLinkBlocks(parsed);
501
+ if (blockMetadata.hashLinkBlock) {
502
+ response.metadata = {
503
+ ...response.metadata,
504
+ ...blockMetadata,
505
+ };
506
+ }
507
+ } catch (error) {
508
+ this.logger.error('Error parsing intermediate steps:', error);
509
+ }
510
+ }
511
+
512
+ if (!response.output || response.output.trim() === '') {
513
+ response.output = 'Agent action complete.';
514
+ }
515
+
516
+ if (response.output) {
517
+ this.smartMemory!.addMessage(new AIMessage(response.output));
518
+ }
519
+
520
+ if (this.tokenTracker) {
521
+ const tokenUsage = this.tokenTracker.getLatestTokenUsage();
522
+ if (tokenUsage) {
523
+ response.tokenUsage = tokenUsage;
524
+ response.cost = calculateTokenCostSync(tokenUsage);
525
+ }
526
+ }
527
+
528
+ const finalMemoryStats = this.smartMemory!.getMemoryStats();
529
+ response.metadata = {
530
+ ...response.metadata,
531
+ memoryStats: {
532
+ activeMessages: finalMemoryStats.totalActiveMessages,
533
+ tokenUsage: finalMemoryStats.currentTokenCount,
534
+ maxTokens: finalMemoryStats.maxTokens,
535
+ usagePercentage: finalMemoryStats.usagePercentage,
536
+ },
537
+ };
538
+
539
+ this.logger.info('LangChainAgent.chat returning response:', response);
540
+ return response;
541
+ }
542
+
543
+ /**
544
+ * Normalize context messages into LangChain message instances and load into memory
545
+ */
546
+ private loadContextMessages(context?: ConversationContext): void {
547
+ if (!this.smartMemory) {
548
+ return;
549
+ }
550
+ if (!context?.messages || context.messages.length === 0) {
551
+ return;
552
+ }
553
+ this.smartMemory.clear();
554
+ const rawMessages = context.messages as unknown[];
555
+ for (const msg of rawMessages) {
556
+ if (msg instanceof HumanMessage || msg instanceof AIMessage) {
557
+ this.smartMemory.addMessage(msg);
558
+ continue;
559
+ }
560
+ if (
561
+ msg &&
562
+ typeof msg === 'object' &&
563
+ 'content' in (msg as Record<string, unknown>) &&
564
+ 'type' in (msg as Record<string, unknown>)
565
+ ) {
566
+ const content = String((msg as { content: unknown }).content);
567
+ const type = String((msg as { type: unknown }).type);
568
+ if (type === 'human') {
569
+ this.smartMemory.addMessage(new HumanMessage(content));
570
+ } else {
571
+ this.smartMemory.addMessage(new AIMessage(content));
572
+ }
573
+ }
574
+ }
575
+ }
35
576
 
36
577
  async boot(): Promise<void> {
578
+ this.logger.info('🚨🚨🚨 LANGCHAIN AGENT BOOT METHOD CALLED 🚨🚨🚨');
579
+
37
580
  if (this.initialized) {
38
581
  this.logger.warn('Agent already initialized');
39
582
  return;
@@ -49,8 +592,100 @@ export class LangChainAgent extends BaseAgent {
49
592
  'gpt-4o-mini';
50
593
  this.tokenTracker = new TokenUsageCallbackHandler(modelName);
51
594
 
595
+ this.toolRegistry = new ToolRegistry(this.logger);
596
+
52
597
  const allTools = this.agentKit.getAggregatedLangChainTools();
53
- this.tools = this.filterTools(allTools);
598
+ this.logger.info('=== TOOL REGISTRATION START ===');
599
+ this.logger.info(
600
+ 'All tools from agentKit:',
601
+ allTools.map((t) => t.name)
602
+ );
603
+
604
+ const filteredTools = this.filterTools(allTools);
605
+ this.logger.info(
606
+ 'Filtered tools for registration:',
607
+ filteredTools.map((t) => t.name)
608
+ );
609
+
610
+ for (const tool of filteredTools) {
611
+ this.logger.info(`🔧 Registering tool: ${tool.name}`);
612
+
613
+ const options: ToolRegistrationOptions = {};
614
+
615
+ const name = tool.name.toLowerCase();
616
+ const desc = tool.description?.toLowerCase() || '';
617
+
618
+ if (
619
+ name.includes('inscribe') ||
620
+ name.includes('hashinal') ||
621
+ desc.includes('inscribe') ||
622
+ desc.includes('hashinal')
623
+ ) {
624
+ options.forceWrapper = true;
625
+ options.metadata = {
626
+ category: 'core' as const,
627
+ version: '1.0.0',
628
+ dependencies: [],
629
+ };
630
+
631
+ this.logger.info(`🎯 CRITICAL TOOL DEBUG - ${tool.name} schema:`, {
632
+ hasSchema: !!tool.schema,
633
+ schemaType: tool.schema?.constructor?.name,
634
+ hasRenderConfig: !!(tool.schema as RenderConfigSchema)
635
+ ?._renderConfig,
636
+ renderConfig: (tool.schema as RenderConfigSchema)?._renderConfig,
637
+ });
638
+ }
639
+
640
+ this.toolRegistry.registerTool(tool, options);
641
+ }
642
+
643
+ this.tools = this.toolRegistry.getAllTools();
644
+
645
+ this.logger.info(`🚀 TOOLS REGISTERED: ${this.tools.length} tools`);
646
+
647
+ const stats = this.toolRegistry.getStatistics();
648
+ this.logger.info('📊 Tool Registry Statistics:', {
649
+ total: stats.totalTools,
650
+ wrapped: stats.wrappedTools,
651
+ unwrapped: stats.unwrappedTools,
652
+ categories: stats.categoryCounts,
653
+ priorities: stats.priorityCounts,
654
+ });
655
+
656
+ const inscriptionTool = this.getInscriptionTool();
657
+ if (inscriptionTool) {
658
+ const entry = this.toolRegistry.getTool(inscriptionTool.name);
659
+ if (entry) {
660
+ this.logger.info(
661
+ `🔒 FINAL SECURITY CHECK - ${inscriptionTool.name} tool type: ${entry.tool.constructor.name}`
662
+ );
663
+ if (entry.tool.constructor.name !== 'FormValidatingToolWrapper') {
664
+ this.logger.error(
665
+ `🚨 SECURITY BREACH: ${inscriptionTool.name} tool is NOT wrapped!`
666
+ );
667
+ throw new Error(
668
+ `Critical security failure: ${inscriptionTool.name} tool bypassed FormValidatingToolWrapper`
669
+ );
670
+ } else {
671
+ this.logger.info(
672
+ `✅ SECURITY VALIDATED: ${inscriptionTool.name} tool is properly wrapped`
673
+ );
674
+ }
675
+ }
676
+ }
677
+
678
+ const toolNames = this.toolRegistry.getToolNames();
679
+ const uniqueNames = new Set(toolNames);
680
+ if (toolNames.length !== uniqueNames.size) {
681
+ this.logger.error('DUPLICATE TOOL NAMES DETECTED in registry!');
682
+ const duplicates = toolNames.filter(
683
+ (name, index) => toolNames.indexOf(name) !== index
684
+ );
685
+ throw new Error(
686
+ `Duplicate tool names detected: ${duplicates.join(', ')}`
687
+ );
688
+ }
54
689
 
55
690
  if (this.config.mcp?.servers && this.config.mcp.servers.length > 0) {
56
691
  if (this.config.mcp.autoConnect !== false) {
@@ -77,6 +712,15 @@ export class LangChainAgent extends BaseAgent {
77
712
  reserveTokens: 10000,
78
713
  });
79
714
 
715
+ this.formEngine = new FormEngine(this.logger);
716
+
717
+ this.executionPipeline = new ExecutionPipeline(
718
+ this.toolRegistry,
719
+ this.formEngine,
720
+ this.smartMemory,
721
+ this.logger
722
+ );
723
+
80
724
  this.systemMessage = this.buildSystemPrompt();
81
725
 
82
726
  this.smartMemory.setSystemPrompt(this.systemMessage);
@@ -84,7 +728,7 @@ export class LangChainAgent extends BaseAgent {
84
728
  await this.createExecutor();
85
729
 
86
730
  this.initialized = true;
87
- this.logger.info('LangChain Hedera agent initialized');
731
+ this.logger.info('LangChain Hedera agent initialized with ToolRegistry');
88
732
  } catch (error) {
89
733
  this.logger.error('Failed to initialize agent:', error);
90
734
  throw error;
@@ -100,20 +744,35 @@ export class LangChainAgent extends BaseAgent {
100
744
  }
101
745
 
102
746
  try {
103
- this.logger.info('LangChainAgent.chat called with:', {
747
+ const toolExecutionResult = await this.handleToolExecution(
104
748
  message,
105
- contextLength: context?.messages?.length || 0,
106
- });
749
+ context
750
+ );
751
+ if (toolExecutionResult) {
752
+ return toolExecutionResult;
753
+ }
107
754
 
108
- if (context?.messages && context.messages.length > 0) {
109
- this.smartMemory.clear();
755
+ const directToolResult = await this.handleDirectToolExecution(message);
756
+ if (directToolResult) {
757
+ return directToolResult;
758
+ }
110
759
 
111
- for (const msg of context.messages) {
112
- this.smartMemory.addMessage(msg);
113
- }
760
+ const jsonToolResult = await this.handleJsonToolCalls(message, context);
761
+ if (jsonToolResult) {
762
+ return jsonToolResult;
114
763
  }
115
764
 
116
- const { HumanMessage } = await import('@langchain/core/messages');
765
+ const contentRefResult = await this.handleContentRefMessages(message);
766
+ if (contentRefResult) {
767
+ return contentRefResult;
768
+ }
769
+
770
+ this.logger.info('LangChainAgent.chat called with:', {
771
+ message,
772
+ contextLength: context?.messages?.length || 0,
773
+ });
774
+
775
+ this.loadContextMessages(context);
117
776
  this.smartMemory.addMessage(new HumanMessage(message));
118
777
 
119
778
  const memoryStats = this.smartMemory.getMemoryStats();
@@ -132,75 +791,7 @@ export class LangChainAgent extends BaseAgent {
132
791
 
133
792
  this.logger.info('LangChainAgent executor result:', result);
134
793
 
135
- let response: ChatResponse = {
136
- output: result.output || '',
137
- message: result.output || '',
138
- notes: [],
139
- intermediateSteps: result.intermediateSteps,
140
- };
141
-
142
- if (result.intermediateSteps && Array.isArray(result.intermediateSteps)) {
143
- const toolCalls = result.intermediateSteps.map(
144
- (step: any, index: number) => ({
145
- id: `call_${index}`,
146
- name: step.action?.tool || 'unknown',
147
- args: step.action?.toolInput || {},
148
- output:
149
- typeof step.observation === 'string'
150
- ? step.observation
151
- : JSON.stringify(step.observation),
152
- })
153
- );
154
-
155
- if (toolCalls.length > 0) {
156
- response.tool_calls = toolCalls;
157
- }
158
- }
159
-
160
- const parsedSteps = result?.intermediateSteps?.[0]?.observation;
161
- if (
162
- parsedSteps &&
163
- typeof parsedSteps === 'string' &&
164
- this.isJSON(parsedSteps)
165
- ) {
166
- try {
167
- const parsed = JSON.parse(parsedSteps);
168
- response = { ...response, ...parsed };
169
- } catch (error) {
170
- this.logger.error('Error parsing intermediate steps:', error);
171
- }
172
- }
173
-
174
- if (!response.output || response.output.trim() === '') {
175
- response.output = 'Agent action complete.';
176
- }
177
-
178
- if (response.output) {
179
- const { AIMessage } = await import('@langchain/core/messages');
180
- this.smartMemory.addMessage(new AIMessage(response.output));
181
- }
182
-
183
- if (this.tokenTracker) {
184
- const tokenUsage = this.tokenTracker.getLatestTokenUsage();
185
- if (tokenUsage) {
186
- response.tokenUsage = tokenUsage;
187
- response.cost = calculateTokenCostSync(tokenUsage);
188
- }
189
- }
190
-
191
- const finalMemoryStats = this.smartMemory.getMemoryStats();
192
- response.metadata = {
193
- ...response.metadata,
194
- memoryStats: {
195
- activeMessages: finalMemoryStats.totalActiveMessages,
196
- tokenUsage: finalMemoryStats.currentTokenCount,
197
- maxTokens: finalMemoryStats.maxTokens,
198
- usagePercentage: finalMemoryStats.usagePercentage,
199
- },
200
- };
201
-
202
- this.logger.info('LangChainAgent.chat returning response:', response);
203
- return response;
794
+ return this.processExecutorResult(result);
204
795
  } catch (error) {
205
796
  this.logger.error('LangChainAgent.chat error:', error);
206
797
  return this.handleError(error);
@@ -217,6 +808,10 @@ export class LangChainAgent extends BaseAgent {
217
808
  this.smartMemory = undefined;
218
809
  }
219
810
 
811
+ if (this.toolRegistry) {
812
+ this.toolRegistry.clear();
813
+ }
814
+
220
815
  this.executor = undefined;
221
816
  this.agentKit = undefined;
222
817
  this.tools = [];
@@ -276,6 +871,242 @@ export class LangChainAgent extends BaseAgent {
276
871
  return new Map(this.mcpConnectionStatus);
277
872
  }
278
873
 
874
+ /**
875
+ * Processes form submission and continues with tool execution
876
+ */
877
+ async processFormSubmission(
878
+ submission: FormSubmission,
879
+ context?: ConversationContext
880
+ ): Promise<ChatResponse> {
881
+ this.logger.info('🔥 LangChainAgent.processFormSubmission START');
882
+
883
+ if (!this.initialized || !this.executor || !this.smartMemory) {
884
+ this.logger.error('🔥 LangChainAgent.processFormSubmission - Agent not initialized');
885
+ throw new Error('Agent not initialized. Call boot() first.');
886
+ }
887
+
888
+ this.logger.info('🔥 LangChainAgent.processFormSubmission - After initialization check');
889
+
890
+ try {
891
+ this.logger.info('🔥 LangChainAgent.processFormSubmission - About to log submission info');
892
+
893
+ this.logger.info('Processing form submission:', {
894
+ formId: submission.formId,
895
+ toolName: submission.toolName,
896
+ parameterKeys: Object.keys(submission.parameters || {}),
897
+ hasParameters: !!submission.parameters,
898
+ parametersType: typeof submission.parameters,
899
+ parametersIsNull: submission.parameters === null,
900
+ parametersIsUndefined: submission.parameters === undefined,
901
+ hasContext: !!submission.context,
902
+ });
903
+
904
+ this.logger.info('🔥 LangChainAgent.processFormSubmission - After submission info logged');
905
+
906
+ if (!submission.parameters || typeof submission.parameters !== 'object') {
907
+ this.logger.error('Invalid form submission parameters:', {
908
+ parameters: submission.parameters,
909
+ type: typeof submission.parameters,
910
+ });
911
+ const errorInfo = JSON.stringify(submission, null, 2);
912
+ return this.handleError(
913
+ new Error(`Invalid form submission parameters: ${errorInfo}`)
914
+ );
915
+ }
916
+
917
+ this.logger.info('🔥 LangChainAgent.processFormSubmission - Parameters validated');
918
+
919
+ this.loadContextMessages(context);
920
+
921
+ this.logger.info('🔥 LangChainAgent.processFormSubmission - Context loaded');
922
+
923
+ const safeSubmission = {
924
+ ...submission,
925
+ parameters: submission.parameters || {},
926
+ };
927
+
928
+ this.logger.info('🔥 LangChainAgent.processFormSubmission - Safe submission created');
929
+
930
+ this.logger.info('About to call executor.processFormSubmission with:', {
931
+ formId: safeSubmission.formId,
932
+ toolName: safeSubmission.toolName,
933
+ parameterKeys: Object.keys(safeSubmission.parameters),
934
+ parameterCount: Object.keys(safeSubmission.parameters).length,
935
+ });
936
+
937
+ const result = await this.executor.processFormSubmission(safeSubmission);
938
+
939
+ this.logger.info('🔍 DEBUG: Raw result from FormAwareAgentExecutor:', {
940
+ hasResult: !!result,
941
+ resultKeys: result ? Object.keys(result) : [],
942
+ hasMetadata: !!result?.metadata,
943
+ metadataKeys: result?.metadata ? Object.keys(result.metadata) : [],
944
+ hasHashLinkBlock: !!(result?.metadata as any)?.hashLinkBlock,
945
+ hashLinkBlockContent: (result?.metadata as any)?.hashLinkBlock
946
+ });
947
+
948
+ if (result?.metadata) {
949
+ this.logger.info('🔍 DEBUG: Full metadata from executor:', JSON.stringify(result.metadata));
950
+ }
951
+
952
+ const preservedMetadata = result?.metadata ? { ...result.metadata } : {};
953
+
954
+ this.logger.info('Executor processFormSubmission result:', {
955
+ hasResult: !!result,
956
+ hasOutput: !!result.output,
957
+ hasError: !!result.error,
958
+ hasMetadata: !!result.metadata,
959
+ outputType: typeof result.output,
960
+ });
961
+
962
+ let outputMessage = 'Form processed successfully.';
963
+ if (typeof result.output === 'string') {
964
+ outputMessage = result.output;
965
+ } else if (result.output) {
966
+ try {
967
+ outputMessage = JSON.stringify(result.output);
968
+ } catch {
969
+ outputMessage = String(result.output);
970
+ }
971
+ }
972
+
973
+ let response: ChatResponse = {
974
+ output: outputMessage,
975
+ message: outputMessage,
976
+ notes: [],
977
+ intermediateSteps: result.intermediateSteps as IntermediateStep[],
978
+ };
979
+
980
+ if (result.metadata) {
981
+ response.metadata = {
982
+ ...response.metadata,
983
+ ...result.metadata
984
+ };
985
+ this.logger.info('🔍 DEBUG: Metadata after merge from result:', {
986
+ hasMetadata: !!response.metadata,
987
+ metadataKeys: response.metadata ? Object.keys(response.metadata) : [],
988
+ hasHashLinkBlock: !!(response.metadata as any)?.hashLinkBlock,
989
+ hashLinkBlockContent: (response.metadata as any)?.hashLinkBlock
990
+ });
991
+ }
992
+
993
+ if (result.requiresForm && result.formMessage) {
994
+ response.formMessage = result.formMessage;
995
+ response.requiresForm = true;
996
+ }
997
+
998
+ if (result.intermediateSteps && Array.isArray(result.intermediateSteps)) {
999
+ const toolCalls = result.intermediateSteps.map(
1000
+ (step: IntermediateStep, index: number) => {
1001
+ const name = step?.action?.tool || 'unknown';
1002
+ const args = step?.action?.toolInput || {};
1003
+ const obs = step?.observation;
1004
+ let output = '';
1005
+ if (typeof obs === 'string') {
1006
+ output = obs;
1007
+ } else if (obs && typeof obs === 'object') {
1008
+ try {
1009
+ output = JSON.stringify(obs);
1010
+ } catch {
1011
+ output = String(obs);
1012
+ }
1013
+ } else if (obs !== undefined) {
1014
+ output = String(obs);
1015
+ }
1016
+ return { id: `call_${index}`, name, args, output };
1017
+ }
1018
+ );
1019
+ if (toolCalls.length > 0) {
1020
+ response.tool_calls = toolCalls;
1021
+ }
1022
+ }
1023
+
1024
+ const parsedSteps = result?.intermediateSteps?.[0]?.observation;
1025
+ if (
1026
+ parsedSteps &&
1027
+ typeof parsedSteps === 'string' &&
1028
+ this.isJSON(parsedSteps)
1029
+ ) {
1030
+ try {
1031
+ const parsed = JSON.parse(parsedSteps);
1032
+ response = { ...response, ...parsed };
1033
+
1034
+ const blockMetadata = this.processHashLinkBlocks(parsed);
1035
+ if (blockMetadata.hashLinkBlock) {
1036
+ response.metadata = {
1037
+ ...response.metadata,
1038
+ ...blockMetadata,
1039
+ };
1040
+ }
1041
+ } catch (error) {
1042
+ this.logger.error('Error parsing intermediate steps:', error);
1043
+ }
1044
+ }
1045
+
1046
+ if (response.output) {
1047
+ this.smartMemory.addMessage(new AIMessage(response.output));
1048
+ }
1049
+
1050
+ if (this.tokenTracker) {
1051
+ const tokenUsage = this.tokenTracker.getLatestTokenUsage();
1052
+ if (tokenUsage) {
1053
+ response.tokenUsage = tokenUsage;
1054
+ response.cost = calculateTokenCostSync(tokenUsage);
1055
+ }
1056
+ }
1057
+
1058
+ const finalMemoryStats = this.smartMemory.getMemoryStats();
1059
+ this.logger.info('🔍 DEBUG: Metadata before memoryStats merge:', {
1060
+ hasMetadata: !!response.metadata,
1061
+ metadataKeys: response.metadata ? Object.keys(response.metadata) : [],
1062
+ hasHashLinkBlock: !!(response.metadata as any)?.hashLinkBlock
1063
+ });
1064
+
1065
+ response.metadata = {
1066
+ ...preservedMetadata,
1067
+ ...response.metadata,
1068
+ memoryStats: {
1069
+ activeMessages: finalMemoryStats.totalActiveMessages,
1070
+ tokenUsage: finalMemoryStats.currentTokenCount,
1071
+ maxTokens: finalMemoryStats.maxTokens,
1072
+ usagePercentage: finalMemoryStats.usagePercentage,
1073
+ },
1074
+ };
1075
+
1076
+ this.logger.info('🔍 DEBUG: Final response metadata before return:', {
1077
+ hasMetadata: !!response.metadata,
1078
+ metadataKeys: response.metadata ? Object.keys(response.metadata) : [],
1079
+ hasHashLinkBlock: !!(response.metadata as any)?.hashLinkBlock,
1080
+ fullMetadata: response.metadata
1081
+ });
1082
+
1083
+ if ((preservedMetadata as any)?.hashLinkBlock && !(response.metadata as any)?.hashLinkBlock) {
1084
+ this.logger.error('❌ CRITICAL: HashLink metadata was lost during processing!');
1085
+ this.logger.error('Original metadata had hashLinkBlock:', (preservedMetadata as any).hashLinkBlock);
1086
+ this.logger.error('Final metadata missing hashLinkBlock');
1087
+ }
1088
+
1089
+ return response;
1090
+ } catch (error) {
1091
+ this.logger.error('Form submission processing error:', error);
1092
+ return this.handleError(error);
1093
+ }
1094
+ }
1095
+
1096
+ /**
1097
+ * Check if the agent has pending forms that need to be completed
1098
+ */
1099
+ hasPendingForms(): boolean {
1100
+ return this.executor ? this.executor.hasPendingForms() : false;
1101
+ }
1102
+
1103
+ /**
1104
+ * Get information about pending forms
1105
+ */
1106
+ getPendingFormsInfo(): Array<{ formId: string; toolName: string }> {
1107
+ return this.executor ? this.executor.getPendingFormsInfo() : [];
1108
+ }
1109
+
279
1110
  private async createAgentKit(): Promise<HederaAgentKit> {
280
1111
  const corePlugins = getAllHederaCorePlugins();
281
1112
  const extensionPlugins = this.config.extensions?.plugins || [];
@@ -299,6 +1130,8 @@ export class LangChainAgent extends BaseAgent {
299
1130
  }
300
1131
 
301
1132
  private async createExecutor(): Promise<void> {
1133
+ const existingPendingForms = this.executor?.getPendingForms() || new Map();
1134
+
302
1135
  let llm: BaseChatModel;
303
1136
  if (this.config.ai?.provider && this.config.ai.provider.getModel) {
304
1137
  llm = this.config.ai.provider.getModel() as BaseChatModel;
@@ -332,18 +1165,60 @@ export class LangChainAgent extends BaseAgent {
332
1165
 
333
1166
  const langchainTools = this.tools as unknown as StructuredTool[];
334
1167
 
1168
+ const inscriptionTool = this.getInscriptionTool();
1169
+ if (inscriptionTool) {
1170
+ const entry = this.toolRegistry.getTool(inscriptionTool.name);
1171
+ if (entry) {
1172
+ this.logger.info(
1173
+ `🔒 FINAL SECURITY CHECK - ${inscriptionTool.name} tool type: ${entry.tool.constructor.name}`
1174
+ );
1175
+ if (entry.tool.constructor.name !== 'FormValidatingToolWrapper') {
1176
+ this.logger.error(
1177
+ `🚨 SECURITY BREACH: ${inscriptionTool.name} tool is NOT wrapped!`
1178
+ );
1179
+ throw new Error(
1180
+ `Critical security failure: ${inscriptionTool.name} tool bypassed FormValidatingToolWrapper`
1181
+ );
1182
+ } else {
1183
+ this.logger.info(
1184
+ `✅ SECURITY VALIDATED: ${inscriptionTool.name} tool is properly wrapped`
1185
+ );
1186
+ }
1187
+ }
1188
+ }
1189
+
1190
+ const stats = this.toolRegistry.getStatistics();
1191
+ this.logger.info('🛡️ TOOL SECURITY REPORT:', {
1192
+ totalTools: stats.totalTools,
1193
+ wrappedTools: stats.wrappedTools,
1194
+ unwrappedTools: stats.unwrappedTools,
1195
+ categories: stats.categoryCounts,
1196
+ priorities: stats.priorityCounts,
1197
+ });
1198
+
1199
+ this.logger.info(
1200
+ `📊 Tool Security Summary: ${stats.wrappedTools} wrapped, ${stats.unwrappedTools} unwrapped`
1201
+ );
1202
+
335
1203
  const agent = await createOpenAIToolsAgent({
336
1204
  llm,
337
1205
  tools: langchainTools,
338
1206
  prompt,
339
1207
  });
340
1208
 
341
- this.executor = new ContentAwareAgentExecutor({
1209
+ this.executor = new FormAwareAgentExecutor({
342
1210
  agent,
343
1211
  tools: langchainTools,
344
1212
  verbose: this.config.debug?.verbose ?? false,
345
1213
  returnIntermediateSteps: true,
346
1214
  });
1215
+
1216
+ if (existingPendingForms.size > 0) {
1217
+ this.logger.info(
1218
+ `Restoring ${existingPendingForms.size} pending forms to new executor`
1219
+ );
1220
+ this.executor.restorePendingForms(existingPendingForms);
1221
+ }
347
1222
  }
348
1223
 
349
1224
  private handleError(error: unknown): ChatResponse {
@@ -443,8 +1318,17 @@ export class LangChainAgent extends BaseAgent {
443
1318
  this.mcpManager,
444
1319
  serverConfig
445
1320
  );
446
- this.tools.push(langchainTool);
1321
+
1322
+ this.toolRegistry.registerTool(langchainTool, {
1323
+ metadata: {
1324
+ category: 'mcp',
1325
+ version: '1.0.0',
1326
+ dependencies: [serverConfig.name],
1327
+ },
1328
+ });
447
1329
  }
1330
+
1331
+ this.tools = this.toolRegistry.getAllTools();
448
1332
  } else {
449
1333
  this.logger.error(
450
1334
  `Failed to connect to MCP server ${status.serverName}: ${status.error}`
@@ -479,7 +1363,7 @@ export class LangChainAgent extends BaseAgent {
479
1363
  /**
480
1364
  * Connect to a single MCP server in background with timeout
481
1365
  */
482
- private connectServerInBackground(serverConfig: any): void {
1366
+ private connectServerInBackground(serverConfig: MCPServerConfig): void {
483
1367
  const serverName = serverConfig.name;
484
1368
 
485
1369
  setTimeout(async () => {
@@ -500,9 +1384,18 @@ export class LangChainAgent extends BaseAgent {
500
1384
  this.mcpManager!,
501
1385
  serverConfig
502
1386
  );
503
- this.tools.push(langchainTool);
1387
+
1388
+ this.toolRegistry.registerTool(langchainTool, {
1389
+ metadata: {
1390
+ category: 'mcp',
1391
+ version: '1.0.0',
1392
+ dependencies: [serverConfig.name],
1393
+ },
1394
+ });
504
1395
  }
505
1396
 
1397
+ this.tools = this.toolRegistry.getAllTools();
1398
+
506
1399
  if (this.initialized && this.executor) {
507
1400
  this.logger.info(
508
1401
  `Recreating executor with ${this.tools.length} total tools`
@@ -530,6 +1423,60 @@ export class LangChainAgent extends BaseAgent {
530
1423
  }, 1000);
531
1424
  }
532
1425
 
1426
+ /**
1427
+ * Detects and processes HashLink blocks from tool responses
1428
+ * @param parsedResponse - The parsed JSON response from a tool
1429
+ * @returns Metadata object containing hashLinkBlock if detected
1430
+ */
1431
+ private processHashLinkBlocks(parsedResponse: unknown): {
1432
+ hashLinkBlock?: Record<string, unknown>;
1433
+ } {
1434
+ try {
1435
+ const responseRecord = parsedResponse as Record<string, unknown>;
1436
+ if (
1437
+ parsedResponse &&
1438
+ typeof parsedResponse === 'object' &&
1439
+ responseRecord.hashLinkBlock &&
1440
+ typeof responseRecord.hashLinkBlock === 'object'
1441
+ ) {
1442
+ const block = responseRecord.hashLinkBlock as Record<string, unknown>;
1443
+
1444
+ if (
1445
+ block.blockId &&
1446
+ block.hashLink &&
1447
+ block.template &&
1448
+ block.attributes &&
1449
+ typeof block.blockId === 'string' &&
1450
+ typeof block.hashLink === 'string' &&
1451
+ typeof block.template === 'string' &&
1452
+ typeof block.attributes === 'object'
1453
+ ) {
1454
+ this.logger.info('HashLink block detected:', {
1455
+ blockId: block.blockId,
1456
+ hashLink: block.hashLink,
1457
+ template: block.template,
1458
+ attributeKeys: Object.keys(block.attributes),
1459
+ });
1460
+
1461
+ return {
1462
+ hashLinkBlock: {
1463
+ blockId: block.blockId,
1464
+ hashLink: block.hashLink,
1465
+ template: block.template,
1466
+ attributes: block.attributes,
1467
+ },
1468
+ };
1469
+ } else {
1470
+ this.logger.warn('Invalid HashLink block structure detected:', block);
1471
+ }
1472
+ }
1473
+ } catch (error) {
1474
+ this.logger.error('Error processing HashLink blocks:', error);
1475
+ }
1476
+
1477
+ return {};
1478
+ }
1479
+
533
1480
  /**
534
1481
  * Check if a string is valid JSON
535
1482
  */