@hashgraphonline/conversational-agent 0.1.215 → 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 (123) hide show
  1. package/dist/cjs/conversational-agent.d.ts +8 -0
  2. package/dist/cjs/core/ToolRegistry.d.ts +130 -0
  3. package/dist/cjs/execution/ExecutionPipeline.d.ts +81 -0
  4. package/dist/cjs/forms/FormEngine.d.ts +121 -0
  5. package/dist/cjs/forms/form-generator.d.ts +39 -2
  6. package/dist/cjs/forms/types.d.ts +21 -2
  7. package/dist/cjs/index.cjs +1 -1
  8. package/dist/cjs/index.cjs.map +1 -1
  9. package/dist/cjs/index.d.ts +3 -4
  10. package/dist/cjs/langchain/FormAwareAgentExecutor.d.ts +53 -4
  11. package/dist/cjs/langchain/FormValidatingToolWrapper.d.ts +43 -6
  12. package/dist/cjs/langchain-agent.d.ts +49 -0
  13. package/dist/cjs/memory/ContentStorage.d.ts +7 -0
  14. package/dist/cjs/memory/SmartMemoryManager.d.ts +1 -0
  15. package/dist/cjs/services/ContentStoreManager.d.ts +11 -1
  16. package/dist/cjs/utils/ResponseFormatter.d.ts +26 -0
  17. package/dist/esm/index.js +2 -6
  18. package/dist/esm/index12.js +1 -1
  19. package/dist/esm/index12.js.map +1 -1
  20. package/dist/esm/index14.js +23 -5
  21. package/dist/esm/index14.js.map +1 -1
  22. package/dist/esm/index15.js +25 -4
  23. package/dist/esm/index15.js.map +1 -1
  24. package/dist/esm/index16.js +4 -2
  25. package/dist/esm/index16.js.map +1 -1
  26. package/dist/esm/index17.js +2 -7
  27. package/dist/esm/index17.js.map +1 -1
  28. package/dist/esm/index18.js +292 -150
  29. package/dist/esm/index18.js.map +1 -1
  30. package/dist/esm/index19.js +158 -65
  31. package/dist/esm/index19.js.map +1 -1
  32. package/dist/esm/index20.js +94 -270
  33. package/dist/esm/index20.js.map +1 -1
  34. package/dist/esm/index21.js +1 -1
  35. package/dist/esm/index23.js +14 -0
  36. package/dist/esm/index23.js.map +1 -1
  37. package/dist/esm/index24.js +508 -12
  38. package/dist/esm/index24.js.map +1 -1
  39. package/dist/esm/index25.js +1 -1
  40. package/dist/esm/index25.js.map +1 -1
  41. package/dist/esm/index26.js +1 -1
  42. package/dist/esm/index26.js.map +1 -1
  43. package/dist/esm/index27.js +189 -128
  44. package/dist/esm/index27.js.map +1 -1
  45. package/dist/esm/index28.js +164 -45
  46. package/dist/esm/index28.js.map +1 -1
  47. package/dist/esm/index29.js +302 -24
  48. package/dist/esm/index29.js.map +1 -1
  49. package/dist/esm/index30.js +144 -80
  50. package/dist/esm/index30.js.map +1 -1
  51. package/dist/esm/index31.js +63 -7
  52. package/dist/esm/index31.js.map +1 -1
  53. package/dist/esm/index32.js +24 -236
  54. package/dist/esm/index32.js.map +1 -1
  55. package/dist/esm/index33.js +95 -0
  56. package/dist/esm/index33.js.map +1 -0
  57. package/dist/esm/index34.js +245 -0
  58. package/dist/esm/index34.js.map +1 -0
  59. package/dist/esm/index5.js.map +1 -1
  60. package/dist/esm/index6.js +61 -22
  61. package/dist/esm/index6.js.map +1 -1
  62. package/dist/esm/index8.js +653 -131
  63. package/dist/esm/index8.js.map +1 -1
  64. package/dist/types/conversational-agent.d.ts +8 -0
  65. package/dist/types/core/ToolRegistry.d.ts +130 -0
  66. package/dist/types/execution/ExecutionPipeline.d.ts +81 -0
  67. package/dist/types/forms/FormEngine.d.ts +121 -0
  68. package/dist/types/forms/form-generator.d.ts +39 -2
  69. package/dist/types/forms/types.d.ts +21 -2
  70. package/dist/types/index.d.ts +3 -4
  71. package/dist/types/langchain/FormAwareAgentExecutor.d.ts +53 -4
  72. package/dist/types/langchain/FormValidatingToolWrapper.d.ts +43 -6
  73. package/dist/types/langchain-agent.d.ts +49 -0
  74. package/dist/types/memory/ContentStorage.d.ts +7 -0
  75. package/dist/types/memory/SmartMemoryManager.d.ts +1 -0
  76. package/dist/types/services/ContentStoreManager.d.ts +11 -1
  77. package/dist/types/utils/ResponseFormatter.d.ts +26 -0
  78. package/package.json +13 -10
  79. package/src/config/system-message.ts +14 -0
  80. package/src/context/ReferenceContextManager.ts +1 -1
  81. package/src/conversational-agent.ts +91 -36
  82. package/src/core/ToolRegistry.ts +358 -0
  83. package/src/execution/ExecutionPipeline.ts +301 -0
  84. package/src/forms/FormEngine.ts +443 -0
  85. package/src/forms/field-type-registry.ts +1 -13
  86. package/src/forms/form-generator.ts +394 -237
  87. package/src/forms/types.ts +20 -3
  88. package/src/index.ts +6 -10
  89. package/src/langchain/FormAwareAgentExecutor.ts +653 -22
  90. package/src/langchain/FormValidatingToolWrapper.ts +216 -93
  91. package/src/langchain-agent.ts +924 -185
  92. package/src/mcp/ContentProcessor.ts +20 -4
  93. package/src/mcp/MCPClientManager.ts +1 -1
  94. package/src/mcp/adapters/langchain.ts +1 -1
  95. package/src/memory/ContentStorage.ts +25 -5
  96. package/src/memory/SmartMemoryManager.ts +27 -4
  97. package/src/memory/TokenCounter.ts +1 -1
  98. package/src/plugins/hbar/HbarPlugin.ts +0 -1
  99. package/src/scripts/test-external-tool-wrapper.ts +3 -12
  100. package/src/scripts/test-hedera-kit-wrapper.ts +6 -22
  101. package/src/scripts/test-inscribe-form-generation.ts +24 -42
  102. package/src/scripts/test-inscribe-wrapper-verification.ts +1 -7
  103. package/src/services/ContentStoreManager.ts +23 -9
  104. package/src/services/EntityResolver.ts +2 -9
  105. package/src/tools/EntityResolverTool.ts +5 -8
  106. package/src/utils/ResponseFormatter.ts +146 -0
  107. package/dist/cjs/examples/external-tool-wrapper-example.d.ts +0 -131
  108. package/dist/cjs/langchain/ContentAwareAgentExecutor.d.ts +0 -14
  109. package/dist/cjs/langchain/external-tool-wrapper.d.ts +0 -179
  110. package/dist/cjs/scripts/test-external-tool-wrapper.d.ts +0 -5
  111. package/dist/cjs/scripts/test-hedera-kit-wrapper.d.ts +0 -36
  112. package/dist/cjs/scripts/test-inscribe-form-generation.d.ts +0 -15
  113. package/dist/cjs/scripts/test-inscribe-wrapper-verification.d.ts +0 -13
  114. package/dist/types/examples/external-tool-wrapper-example.d.ts +0 -131
  115. package/dist/types/langchain/ContentAwareAgentExecutor.d.ts +0 -14
  116. package/dist/types/langchain/external-tool-wrapper.d.ts +0 -179
  117. package/dist/types/scripts/test-external-tool-wrapper.d.ts +0 -5
  118. package/dist/types/scripts/test-hedera-kit-wrapper.d.ts +0 -36
  119. package/dist/types/scripts/test-inscribe-form-generation.d.ts +0 -15
  120. package/dist/types/scripts/test-inscribe-wrapper-verification.d.ts +0 -13
  121. package/src/examples/external-tool-wrapper-example.ts +0 -227
  122. package/src/langchain/ContentAwareAgentExecutor.ts +0 -19
  123. package/src/langchain/external-tool-wrapper.ts +0 -486
@@ -1,7 +1,6 @@
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 { z } from 'zod';
5
4
  import { FormAwareAgentExecutor } from './langchain/FormAwareAgentExecutor';
6
5
  import {
7
6
  ChatPromptTemplate,
@@ -25,10 +24,53 @@ import {
25
24
  import { MCPClientManager } from './mcp/MCPClientManager';
26
25
  import { convertMCPToolToLangChain } from './mcp/adapters/langchain';
27
26
  import { SmartMemoryManager } from './memory/SmartMemoryManager';
28
- import type { MCPConnectionStatus } from './mcp/types';
27
+ import type { MCPConnectionStatus, MCPServerConfig } from './mcp/types';
28
+ import { ResponseFormatter } from './utils/ResponseFormatter';
29
29
  import type { FormSubmission } from './forms/types';
30
- import { FormGenerator } from './forms/form-generator';
31
- import { wrapToolWithFormValidation } from './langchain/FormValidatingToolWrapper';
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
+ }
32
74
 
33
75
  export class LangChainAgent extends BaseAgent {
34
76
  private executor: FormAwareAgentExecutor | undefined;
@@ -36,11 +78,505 @@ export class LangChainAgent extends BaseAgent {
36
78
  private mcpManager?: MCPClientManager;
37
79
  private smartMemory: SmartMemoryManager | undefined;
38
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
+ }
39
576
 
40
577
  async boot(): Promise<void> {
41
- console.log('🚨🚨🚨 LANGCHAIN AGENT BOOT METHOD CALLED 🚨🚨🚨');
42
578
  this.logger.info('🚨🚨🚨 LANGCHAIN AGENT BOOT METHOD CALLED 🚨🚨🚨');
43
-
579
+
44
580
  if (this.initialized) {
45
581
  this.logger.warn('Agent already initialized');
46
582
  return;
@@ -56,77 +592,100 @@ export class LangChainAgent extends BaseAgent {
56
592
  'gpt-4o-mini';
57
593
  this.tokenTracker = new TokenUsageCallbackHandler(modelName);
58
594
 
595
+ this.toolRegistry = new ToolRegistry(this.logger);
596
+
59
597
  const allTools = this.agentKit.getAggregatedLangChainTools();
60
- this.logger.info('=== TOOL WRAPPING DEBUG START ===');
61
- this.logger.info('All tools from agentKit:', allTools.map(t => t.name));
62
-
598
+ this.logger.info('=== TOOL REGISTRATION START ===');
599
+ this.logger.info(
600
+ 'All tools from agentKit:',
601
+ allTools.map((t) => t.name)
602
+ );
603
+
63
604
  const filteredTools = this.filterTools(allTools);
64
- this.logger.info('All filtered tools:', filteredTools.map(t => t.name));
65
-
66
- const formGenerator = new FormGenerator();
67
-
68
- this.tools = filteredTools.map(tool => {
69
- this.logger.info(`Processing tool: ${tool.name}`);
70
- console.log(`🔧 Processing tool: ${tool.name}`);
71
-
72
- if (tool.name === 'inscribeHashinal') {
73
- this.logger.info(`Debug InscribeHashinalTool schema:`, {
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:`, {
74
632
  hasSchema: !!tool.schema,
75
633
  schemaType: tool.schema?.constructor?.name,
76
- hasRenderConfig: !!(tool.schema as any)?._renderConfig,
77
- renderConfig: (tool.schema as any)?._renderConfig
634
+ hasRenderConfig: !!(tool.schema as RenderConfigSchema)
635
+ ?._renderConfig,
636
+ renderConfig: (tool.schema as RenderConfigSchema)?._renderConfig,
78
637
  });
79
638
  }
80
-
81
- // Force schema getter to be called to ensure _renderConfig is set
82
- const schema = tool.schema;
83
- let hasRenderConfig = (schema as any)?._renderConfig;
84
-
85
- // Special handling for InscribeHashinalTool
86
- if (tool.name === 'inscribeHashinal') {
87
- this.logger.info(`InscribeHashinalTool detected`, {
88
- hasRenderConfig: !!hasRenderConfig,
89
- schemaType: schema?.constructor?.name
90
- });
91
-
92
- if (!hasRenderConfig) {
93
- // Ensure render config is set for form generation
94
- (schema as any)._renderConfig = {
95
- fieldType: 'object',
96
- ui: {
97
- label: 'Inscribe Hashinal NFT',
98
- description: 'Create a Hashinal inscription for NFT minting'
99
- }
100
- };
101
- hasRenderConfig = true;
102
- this.logger.info(`Added render config to InscribeHashinalTool for form generation`);
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
+ );
103
674
  }
104
675
  }
105
-
106
- // Check if schema is ZodObject or behaves like one (including ZodSchemaWithRender)
107
- const isZodObjectLike = (schema: any): boolean => {
108
- if (!schema || typeof schema !== 'object') {
109
- return false;
110
- }
111
- return (
112
- schema instanceof z.ZodObject ||
113
- schema._def?.typeName === 'ZodObject' ||
114
- ('shape' in schema && typeof schema.shape === 'object')
115
- );
116
- };
676
+ }
117
677
 
118
- if (hasRenderConfig && isZodObjectLike(tool.schema)) {
119
- this.logger.info(`Wrapping tool ${tool.name} with form validation`);
120
- return wrapToolWithFormValidation(tool as StructuredTool<z.ZodObject<z.ZodRawShape, z.UnknownKeysParam, z.ZodTypeAny>>, formGenerator, {
121
- requireAllFields: false,
122
- skipFields: ['metaOptions']
123
- });
124
- }
125
- return tool;
126
- });
127
-
128
- console.log(`🚀 TOOLS ASSIGNED: ${this.tools.length} tools`);
129
- this.logger.info(`🚀 TOOLS ASSIGNED: ${this.tools.length} tools`);
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
+ }
130
689
 
131
690
  if (this.config.mcp?.servers && this.config.mcp.servers.length > 0) {
132
691
  if (this.config.mcp.autoConnect !== false) {
@@ -153,6 +712,15 @@ export class LangChainAgent extends BaseAgent {
153
712
  reserveTokens: 10000,
154
713
  });
155
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
+
156
724
  this.systemMessage = this.buildSystemPrompt();
157
725
 
158
726
  this.smartMemory.setSystemPrompt(this.systemMessage);
@@ -160,7 +728,7 @@ export class LangChainAgent extends BaseAgent {
160
728
  await this.createExecutor();
161
729
 
162
730
  this.initialized = true;
163
- this.logger.info('LangChain Hedera agent initialized');
731
+ this.logger.info('LangChain Hedera agent initialized with ToolRegistry');
164
732
  } catch (error) {
165
733
  this.logger.error('Failed to initialize agent:', error);
166
734
  throw error;
@@ -176,20 +744,35 @@ export class LangChainAgent extends BaseAgent {
176
744
  }
177
745
 
178
746
  try {
179
- this.logger.info('LangChainAgent.chat called with:', {
747
+ const toolExecutionResult = await this.handleToolExecution(
180
748
  message,
181
- contextLength: context?.messages?.length || 0,
182
- });
749
+ context
750
+ );
751
+ if (toolExecutionResult) {
752
+ return toolExecutionResult;
753
+ }
754
+
755
+ const directToolResult = await this.handleDirectToolExecution(message);
756
+ if (directToolResult) {
757
+ return directToolResult;
758
+ }
183
759
 
184
- if (context?.messages && context.messages.length > 0) {
185
- this.smartMemory.clear();
760
+ const jsonToolResult = await this.handleJsonToolCalls(message, context);
761
+ if (jsonToolResult) {
762
+ return jsonToolResult;
763
+ }
186
764
 
187
- for (const msg of context.messages) {
188
- this.smartMemory.addMessage(msg);
189
- }
765
+ const contentRefResult = await this.handleContentRefMessages(message);
766
+ if (contentRefResult) {
767
+ return contentRefResult;
190
768
  }
191
769
 
192
- const { HumanMessage } = await import('@langchain/core/messages');
770
+ this.logger.info('LangChainAgent.chat called with:', {
771
+ message,
772
+ contextLength: context?.messages?.length || 0,
773
+ });
774
+
775
+ this.loadContextMessages(context);
193
776
  this.smartMemory.addMessage(new HumanMessage(message));
194
777
 
195
778
  const memoryStats = this.smartMemory.getMemoryStats();
@@ -208,86 +791,7 @@ export class LangChainAgent extends BaseAgent {
208
791
 
209
792
  this.logger.info('LangChainAgent executor result:', result);
210
793
 
211
- const outputStr = typeof result.output === 'string'
212
- ? result.output
213
- : result.output
214
- ? JSON.stringify(result.output)
215
- : '';
216
-
217
- let response: ChatResponse = {
218
- output: outputStr,
219
- message: outputStr,
220
- notes: [],
221
- intermediateSteps: result.intermediateSteps,
222
- };
223
-
224
- if (result.requiresForm && result.formMessage) {
225
- response.formMessage = result.formMessage;
226
- response.requiresForm = true;
227
- }
228
-
229
- if (result.intermediateSteps && Array.isArray(result.intermediateSteps)) {
230
- const toolCalls = result.intermediateSteps.map(
231
- (step: any, index: number) => ({
232
- id: `call_${index}`,
233
- name: step.action?.tool || 'unknown',
234
- args: step.action?.toolInput || {},
235
- output:
236
- typeof step.observation === 'string'
237
- ? step.observation
238
- : JSON.stringify(step.observation),
239
- })
240
- );
241
-
242
- if (toolCalls.length > 0) {
243
- response.tool_calls = toolCalls;
244
- }
245
- }
246
-
247
- const parsedSteps = result?.intermediateSteps?.[0]?.observation;
248
- if (
249
- parsedSteps &&
250
- typeof parsedSteps === 'string' &&
251
- this.isJSON(parsedSteps)
252
- ) {
253
- try {
254
- const parsed = JSON.parse(parsedSteps);
255
- response = { ...response, ...parsed };
256
- } catch (error) {
257
- this.logger.error('Error parsing intermediate steps:', error);
258
- }
259
- }
260
-
261
- if (!response.output || response.output.trim() === '') {
262
- response.output = 'Agent action complete.';
263
- }
264
-
265
- if (response.output) {
266
- const { AIMessage } = await import('@langchain/core/messages');
267
- this.smartMemory.addMessage(new AIMessage(response.output));
268
- }
269
-
270
- if (this.tokenTracker) {
271
- const tokenUsage = this.tokenTracker.getLatestTokenUsage();
272
- if (tokenUsage) {
273
- response.tokenUsage = tokenUsage;
274
- response.cost = calculateTokenCostSync(tokenUsage);
275
- }
276
- }
277
-
278
- const finalMemoryStats = this.smartMemory.getMemoryStats();
279
- response.metadata = {
280
- ...response.metadata,
281
- memoryStats: {
282
- activeMessages: finalMemoryStats.totalActiveMessages,
283
- tokenUsage: finalMemoryStats.currentTokenCount,
284
- maxTokens: finalMemoryStats.maxTokens,
285
- usagePercentage: finalMemoryStats.usagePercentage,
286
- },
287
- };
288
-
289
- this.logger.info('LangChainAgent.chat returning response:', response);
290
- return response;
794
+ return this.processExecutorResult(result);
291
795
  } catch (error) {
292
796
  this.logger.error('LangChainAgent.chat error:', error);
293
797
  return this.handleError(error);
@@ -304,6 +808,10 @@ export class LangChainAgent extends BaseAgent {
304
808
  this.smartMemory = undefined;
305
809
  }
306
810
 
811
+ if (this.toolRegistry) {
812
+ this.toolRegistry.clear();
813
+ }
814
+
307
815
  this.executor = undefined;
308
816
  this.agentKit = undefined;
309
817
  this.tools = [];
@@ -370,38 +878,118 @@ export class LangChainAgent extends BaseAgent {
370
878
  submission: FormSubmission,
371
879
  context?: ConversationContext
372
880
  ): Promise<ChatResponse> {
881
+ this.logger.info('🔥 LangChainAgent.processFormSubmission START');
882
+
373
883
  if (!this.initialized || !this.executor || !this.smartMemory) {
884
+ this.logger.error('🔥 LangChainAgent.processFormSubmission - Agent not initialized');
374
885
  throw new Error('Agent not initialized. Call boot() first.');
375
886
  }
376
887
 
888
+ this.logger.info('🔥 LangChainAgent.processFormSubmission - After initialization check');
889
+
377
890
  try {
891
+ this.logger.info('🔥 LangChainAgent.processFormSubmission - About to log submission info');
892
+
378
893
  this.logger.info('Processing form submission:', {
379
894
  formId: submission.formId,
380
- dataKeys: Object.keys(submission.data)
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,
381
902
  });
382
903
 
383
- if (context?.messages && context.messages.length > 0) {
384
- this.smartMemory.clear();
385
- for (const msg of context.messages) {
386
- this.smartMemory.addMessage(msg);
387
- }
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
+ );
388
915
  }
389
916
 
390
- const result = await this.executor.processFormSubmission(submission);
917
+ this.logger.info('🔥 LangChainAgent.processFormSubmission - Parameters validated');
918
+
919
+ this.loadContextMessages(context);
391
920
 
392
- const outputMessage = typeof result.output === 'string'
393
- ? result.output
394
- : result.output
395
- ? JSON.stringify(result.output)
396
- : 'Form processed successfully.';
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
+ }
397
972
 
398
973
  let response: ChatResponse = {
399
974
  output: outputMessage,
400
975
  message: outputMessage,
401
976
  notes: [],
402
- intermediateSteps: result.intermediateSteps as any
977
+ intermediateSteps: result.intermediateSteps as IntermediateStep[],
403
978
  };
404
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
+
405
993
  if (result.requiresForm && result.formMessage) {
406
994
  response.formMessage = result.formMessage;
407
995
  response.requiresForm = true;
@@ -409,17 +997,25 @@ export class LangChainAgent extends BaseAgent {
409
997
 
410
998
  if (result.intermediateSteps && Array.isArray(result.intermediateSteps)) {
411
999
  const toolCalls = result.intermediateSteps.map(
412
- (step: any, index: number) => ({
413
- id: `call_${index}`,
414
- name: step.action?.tool || 'unknown',
415
- args: step.action?.toolInput || {},
416
- output:
417
- typeof step.observation === 'string'
418
- ? step.observation
419
- : JSON.stringify(step.observation),
420
- })
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
+ }
421
1018
  );
422
-
423
1019
  if (toolCalls.length > 0) {
424
1020
  response.tool_calls = toolCalls;
425
1021
  }
@@ -434,13 +1030,20 @@ export class LangChainAgent extends BaseAgent {
434
1030
  try {
435
1031
  const parsed = JSON.parse(parsedSteps);
436
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
+ }
437
1041
  } catch (error) {
438
1042
  this.logger.error('Error parsing intermediate steps:', error);
439
1043
  }
440
1044
  }
441
1045
 
442
1046
  if (response.output) {
443
- const { AIMessage } = await import('@langchain/core/messages');
444
1047
  this.smartMemory.addMessage(new AIMessage(response.output));
445
1048
  }
446
1049
 
@@ -453,7 +1056,14 @@ export class LangChainAgent extends BaseAgent {
453
1056
  }
454
1057
 
455
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
+
456
1065
  response.metadata = {
1066
+ ...preservedMetadata,
457
1067
  ...response.metadata,
458
1068
  memoryStats: {
459
1069
  activeMessages: finalMemoryStats.totalActiveMessages,
@@ -462,6 +1072,19 @@ export class LangChainAgent extends BaseAgent {
462
1072
  usagePercentage: finalMemoryStats.usagePercentage,
463
1073
  },
464
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
+ }
465
1088
 
466
1089
  return response;
467
1090
  } catch (error) {
@@ -507,6 +1130,8 @@ export class LangChainAgent extends BaseAgent {
507
1130
  }
508
1131
 
509
1132
  private async createExecutor(): Promise<void> {
1133
+ const existingPendingForms = this.executor?.getPendingForms() || new Map();
1134
+
510
1135
  let llm: BaseChatModel;
511
1136
  if (this.config.ai?.provider && this.config.ai.provider.getModel) {
512
1137
  llm = this.config.ai.provider.getModel() as BaseChatModel;
@@ -540,6 +1165,41 @@ export class LangChainAgent extends BaseAgent {
540
1165
 
541
1166
  const langchainTools = this.tools as unknown as StructuredTool[];
542
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
+
543
1203
  const agent = await createOpenAIToolsAgent({
544
1204
  llm,
545
1205
  tools: langchainTools,
@@ -552,6 +1212,13 @@ export class LangChainAgent extends BaseAgent {
552
1212
  verbose: this.config.debug?.verbose ?? false,
553
1213
  returnIntermediateSteps: true,
554
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
+ }
555
1222
  }
556
1223
 
557
1224
  private handleError(error: unknown): ChatResponse {
@@ -651,8 +1318,17 @@ export class LangChainAgent extends BaseAgent {
651
1318
  this.mcpManager,
652
1319
  serverConfig
653
1320
  );
654
- 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
+ });
655
1329
  }
1330
+
1331
+ this.tools = this.toolRegistry.getAllTools();
656
1332
  } else {
657
1333
  this.logger.error(
658
1334
  `Failed to connect to MCP server ${status.serverName}: ${status.error}`
@@ -687,7 +1363,7 @@ export class LangChainAgent extends BaseAgent {
687
1363
  /**
688
1364
  * Connect to a single MCP server in background with timeout
689
1365
  */
690
- private connectServerInBackground(serverConfig: any): void {
1366
+ private connectServerInBackground(serverConfig: MCPServerConfig): void {
691
1367
  const serverName = serverConfig.name;
692
1368
 
693
1369
  setTimeout(async () => {
@@ -708,9 +1384,18 @@ export class LangChainAgent extends BaseAgent {
708
1384
  this.mcpManager!,
709
1385
  serverConfig
710
1386
  );
711
- 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
+ });
712
1395
  }
713
1396
 
1397
+ this.tools = this.toolRegistry.getAllTools();
1398
+
714
1399
  if (this.initialized && this.executor) {
715
1400
  this.logger.info(
716
1401
  `Recreating executor with ${this.tools.length} total tools`
@@ -738,6 +1423,60 @@ export class LangChainAgent extends BaseAgent {
738
1423
  }, 1000);
739
1424
  }
740
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
+
741
1480
  /**
742
1481
  * Check if a string is valid JSON
743
1482
  */