@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
@@ -0,0 +1,971 @@
1
+ import { AgentExecutor } from 'langchain/agents';
2
+ import { ZodError, z } from 'zod';
3
+ import { FormGenerator } from '../forms/form-generator';
4
+ import type { FormMessage, FormSubmission } from '../forms/types';
5
+ import { FormEngine } from '../forms/FormEngine';
6
+ import { Logger } from '@hashgraphonline/standards-sdk';
7
+ import type { AgentAction, AgentFinish, AgentStep } from 'langchain/agents';
8
+ import type { ToolInterface } from '@langchain/core/tools';
9
+ import { isFormValidatable } from '@hashgraphonline/standards-agent-kit';
10
+ import type { ChainValues } from '@langchain/core/utils/types';
11
+ import type { CallbackManagerForChainRun } from '@langchain/core/callbacks/manager';
12
+ import type { RunnableConfig } from '@langchain/core/runnables';
13
+ import { ResponseFormatter } from '../utils/ResponseFormatter';
14
+
15
+ const globalPendingForms: Map<string, PendingFormData> = new Map();
16
+
17
+ interface HashLinkBlock {
18
+ blockId: string;
19
+ hashLink: string;
20
+ template: string;
21
+ attributes: Record<string, unknown>;
22
+ }
23
+
24
+ interface HashLinkResponse {
25
+ hasHashLinkBlocks: boolean;
26
+ hashLinkBlock?: HashLinkBlock;
27
+ message: string;
28
+ }
29
+
30
+ interface PendingFormData {
31
+ toolName: string;
32
+ originalInput: unknown;
33
+ originalToolInput?: unknown;
34
+ schema: unknown;
35
+ toolRef?: ToolInterface | undefined;
36
+ originalToolRef?: { call?: (args: Record<string, unknown>) => Promise<string> } | undefined;
37
+ }
38
+
39
+ interface ToolResponse {
40
+ requiresForm?: boolean;
41
+ formMessage?: {
42
+ id: string;
43
+ [key: string]: unknown;
44
+ };
45
+ message?: string;
46
+ hashLinkBlock?: HashLinkBlock;
47
+ success?: boolean;
48
+ inscription?: Record<string, unknown>;
49
+ metadata?: Record<string, unknown>;
50
+ }
51
+
52
+ interface IntermediateStepData {
53
+ action?: {
54
+ tool?: string;
55
+ [key: string]: unknown;
56
+ };
57
+ observation?: unknown;
58
+ }
59
+
60
+ /**
61
+ * Agent executor that intercepts Zod validation errors and generates forms,
62
+ * and processes HashLink block responses for rich UI rendering
63
+ */
64
+ export class FormAwareAgentExecutor extends AgentExecutor {
65
+ private formGenerator: FormGenerator;
66
+ private formEngine: FormEngine;
67
+ private formLogger: Logger;
68
+ private pendingForms: Map<string, PendingFormData> = new Map();
69
+
70
+ constructor(...args: ConstructorParameters<typeof AgentExecutor>) {
71
+ super(...args);
72
+ this.formGenerator = new FormGenerator();
73
+ this.formEngine = new FormEngine(new Logger({ module: 'FormAwareAgentExecutor.FormEngine' }));
74
+ this.formLogger = new Logger({ module: 'FormAwareAgentExecutor' });
75
+ }
76
+
77
+ /**
78
+ * BULLETPROOF TOOL INTERCEPTION
79
+ * Override the single-step execution to intercept tool calls BEFORE LangChain processes them
80
+ */
81
+ override async _takeNextStep(
82
+ nameToolMap: Record<string, ToolInterface>,
83
+ inputs: ChainValues,
84
+ intermediateSteps: AgentStep[],
85
+ runManager?: CallbackManagerForChainRun,
86
+ config?: RunnableConfig
87
+ ): Promise<AgentFinish | AgentStep[]> {
88
+ this.formLogger.info('🛡️ BULLETPROOF INTERCEPTION: _takeNextStep called', {
89
+ availableTools: Object.keys(nameToolMap),
90
+ inputKeys: Object.keys(inputs)
91
+ });
92
+
93
+ const result = await this.agent.plan(intermediateSteps, inputs, runManager?.getChild());
94
+
95
+ if ('returnValues' in result) {
96
+ this.formLogger.info('Agent returned finish action, passing through');
97
+ return result;
98
+ }
99
+
100
+ const action = result as AgentAction;
101
+ const toolName = action.tool;
102
+ const toolInput = action.toolInput;
103
+
104
+ this.formLogger.info(`🎯 INTERCEPTING TOOL CALL: ${toolName}`, {
105
+ toolInput,
106
+ hasInNameToolMap: toolName in nameToolMap,
107
+ toolInputKeys: Object.keys(toolInput || {})
108
+ });
109
+
110
+ const tool = nameToolMap[toolName] || this.tools.find(t => t.name === toolName);
111
+
112
+ if (!tool) {
113
+ this.formLogger.error(`Tool ${toolName} not found in registry`);
114
+ throw new Error(`Tool "${toolName}" not found`);
115
+ }
116
+
117
+ let shouldGenerateForm = false;
118
+
119
+ if (isFormValidatable(tool)) {
120
+ this.formLogger.info(`🔍 Tool ${toolName} implements FormValidatable, checking shouldGenerateForm()`, {
121
+ toolInput
122
+ });
123
+
124
+ try {
125
+ shouldGenerateForm = tool.shouldGenerateForm(toolInput);
126
+ this.formLogger.info(`FormValidatable.shouldGenerateForm() result: ${shouldGenerateForm}`, {
127
+ toolName,
128
+ toolInput
129
+ });
130
+ } catch (error) {
131
+ this.formLogger.error(`Error calling shouldGenerateForm() on ${toolName}:`, error);
132
+ shouldGenerateForm = false;
133
+ }
134
+ }
135
+
136
+ if (shouldGenerateForm) {
137
+ this.formLogger.info(`🚨 FORM GENERATION TRIGGERED for ${toolName}`);
138
+
139
+ try {
140
+ let schemaToUse: z.ZodSchema;
141
+ let isFocusedSchema = false;
142
+
143
+ if (isFormValidatable(tool)) {
144
+ this.formLogger.info(`🎯 Tool ${toolName} is FormValidatable, attempting to get focused schema`);
145
+ try {
146
+ const focusedSchema = tool.getFormSchema();
147
+ if (focusedSchema) {
148
+ schemaToUse = focusedSchema;
149
+ isFocusedSchema = true;
150
+ this.formLogger.info(`✅ Successfully obtained focused schema for ${toolName}`);
151
+ } else {
152
+ this.formLogger.warn(`getFormSchema() returned null/undefined for ${toolName}, using default schema`);
153
+ schemaToUse = tool.schema;
154
+ isFocusedSchema = false;
155
+ }
156
+ } catch (error) {
157
+ this.formLogger.error(`Failed to get focused schema from ${toolName}:`, error);
158
+ this.formLogger.info(`Falling back to default schema for ${toolName}`);
159
+ schemaToUse = tool.schema;
160
+ isFocusedSchema = false;
161
+ }
162
+ } else {
163
+ this.formLogger.info(`Tool ${toolName} is not FormValidatable, using default schema`);
164
+ schemaToUse = tool.schema;
165
+ isFocusedSchema = false;
166
+ }
167
+
168
+ let schemaFieldCount = 'unknown';
169
+ try {
170
+ if (schemaToUse && typeof schemaToUse === 'object' && 'shape' in schemaToUse) {
171
+ const shape = (schemaToUse as any).shape;
172
+ if (shape && typeof shape === 'object') {
173
+ schemaFieldCount = Object.keys(shape).length.toString();
174
+ }
175
+ }
176
+ } catch {
177
+ }
178
+
179
+ this.formLogger.info(`📋 Generating form with ${isFocusedSchema ? 'FOCUSED' : 'DEFAULT'} schema`, {
180
+ toolName,
181
+ schemaType: schemaToUse?.constructor?.name,
182
+ estimatedFieldCount: schemaFieldCount,
183
+ isFocusedSchema
184
+ });
185
+
186
+ let missingFields: Set<string> | undefined;
187
+
188
+ if (isFocusedSchema) {
189
+ this.formLogger.info(`⭐ Using focused schema - letting FormGenerator determine fields from schema`);
190
+ missingFields = undefined;
191
+ } else {
192
+ missingFields = new Set<string>();
193
+ if (this.isZodObject(schemaToUse)) {
194
+ const zodObject = schemaToUse as z.ZodObject<z.ZodRawShape>;
195
+ const shape = zodObject.shape || {};
196
+ for (const fieldName of Object.keys(shape)) {
197
+ const value = (toolInput || {})[fieldName];
198
+
199
+ const isEmpty = (isFormValidatable(tool) && tool.isFieldEmpty)
200
+ ? tool.isFieldEmpty(fieldName, value)
201
+ : (value === undefined ||
202
+ value === '' ||
203
+ value === null ||
204
+ (Array.isArray(value) && value.length === 0));
205
+
206
+ const isRequired = this.isFieldRequired(schemaToUse, fieldName);
207
+
208
+ const isEssential = (isFormValidatable(tool) && tool.getEssentialFields)
209
+ ? tool.getEssentialFields().includes(fieldName)
210
+ : false;
211
+
212
+ this.formLogger.info(`🔍 Field analysis: ${fieldName}`, {
213
+ value: value,
214
+ isEmpty: isEmpty,
215
+ isRequired: isRequired,
216
+ isEssential: isEssential,
217
+ willAddToMissingFields: isEmpty && (isRequired || isEssential)
218
+ });
219
+
220
+ if (isEmpty && (isRequired || isEssential)) {
221
+ missingFields.add(fieldName);
222
+ }
223
+ }
224
+ }
225
+
226
+ this.formLogger.info(`📋 Missing fields analysis complete`, {
227
+ totalFields: this.isZodObject(schemaToUse) ? Object.keys(schemaToUse.shape).length : 0,
228
+ missingFieldsCount: missingFields.size,
229
+ missingFields: Array.from(missingFields)
230
+ });
231
+ }
232
+
233
+ const formMessage = await this.formGenerator.generateFormFromSchema(
234
+ schemaToUse,
235
+ toolInput,
236
+ {
237
+ toolName: toolName,
238
+ toolDescription: tool.description
239
+ },
240
+ missingFields // Pass undefined for focused schemas, Set<string> for others
241
+ );
242
+
243
+ if (this.isZodObject(schemaToUse)) {
244
+ try {
245
+ const { jsonSchema, uiSchema } = this.formGenerator.generateJsonSchemaForm(
246
+ schemaToUse,
247
+ toolInput as Record<string, unknown> | undefined,
248
+ missingFields
249
+ );
250
+ formMessage.jsonSchema = jsonSchema;
251
+ formMessage.uiSchema = uiSchema;
252
+ } catch (error) {
253
+ this.formLogger.warn('Failed to generate JSON Schema for RJSF:', error);
254
+ }
255
+ }
256
+
257
+ formMessage.partialInput = toolInput;
258
+
259
+ const formData: PendingFormData = {
260
+ toolName: toolName,
261
+ originalInput: inputs,
262
+ originalToolInput: toolInput,
263
+ schema: schemaToUse,
264
+ toolRef: tool as ToolInterface | undefined,
265
+ originalToolRef: (tool as unknown as { originalTool?: { call?: (args: Record<string, unknown>) => Promise<string> } }).originalTool
266
+ };
267
+ this.pendingForms.set(formMessage.id, formData);
268
+ globalPendingForms.set(formMessage.id, formData);
269
+
270
+ this.formLogger.info(`✅ FORM INTERCEPT SUCCESS for ${toolName}`);
271
+
272
+ const formResult = {
273
+ requiresForm: true,
274
+ formMessage
275
+ };
276
+
277
+ return [{
278
+ action: action,
279
+ observation: JSON.stringify(formResult)
280
+ }];
281
+
282
+ } catch (error) {
283
+ this.formLogger.error(`Form generation failed for ${toolName}:`, error);
284
+
285
+ }
286
+ }
287
+
288
+ this.formLogger.info(`⚪ Passing through to normal tool execution for ${toolName}`);
289
+ return super._takeNextStep(nameToolMap, inputs, intermediateSteps, runManager, config);
290
+ }
291
+
292
+ /**
293
+ * Type guard to check if a schema is a ZodObject
294
+ */
295
+ private isZodObject(schema: unknown): schema is z.ZodObject<z.ZodRawShape> {
296
+ if (!schema || typeof schema !== 'object') {
297
+ return false;
298
+ }
299
+ const candidate = schema as { _def?: { typeName?: string } };
300
+ return Boolean(candidate._def && candidate._def.typeName === 'ZodObject');
301
+ }
302
+
303
+ /**
304
+ * Helper to determine if a field is required in the schema
305
+ */
306
+ private isFieldRequired(schema: unknown, fieldPath: string): boolean {
307
+ if (!schema || !fieldPath) {
308
+ return false;
309
+ }
310
+
311
+ try {
312
+ const obj = schema as { _def?: { typeName?: string; shape?: unknown } };
313
+ const def = obj._def;
314
+ if (!def || def.typeName !== 'ZodObject') {
315
+ return false;
316
+ }
317
+ const rawShape: unknown = typeof (def as { shape?: unknown }).shape === 'function'
318
+ ? (def as { shape: () => Record<string, z.ZodTypeAny> }).shape()
319
+ : (def as { shape?: Record<string, z.ZodTypeAny> }).shape;
320
+ if (!rawShape || typeof rawShape !== 'object') {
321
+ return false;
322
+ }
323
+ const shape = rawShape as Record<string, z.ZodTypeAny>;
324
+ const fieldSchema = shape[fieldPath];
325
+ if (!fieldSchema) {
326
+ return false;
327
+ }
328
+ const unwrapOptional = (s: z.ZodTypeAny): z.ZodTypeAny => {
329
+ const inner = (s as unknown as { _def?: { typeName?: string; innerType?: z.ZodTypeAny } })._def;
330
+ if (inner && inner.typeName === 'ZodOptional' && inner.innerType) {
331
+ return inner.innerType;
332
+ }
333
+ return s;
334
+ };
335
+ const unwrapped = unwrapOptional(fieldSchema);
336
+ const fdef = (unwrapped as unknown as { _def?: { typeName?: string; defaultValue?: unknown } })._def;
337
+ if (!fdef) {
338
+ return true;
339
+ }
340
+ if (fdef.typeName === 'ZodOptional' || fdef.typeName === 'ZodDefault') {
341
+ return false;
342
+ }
343
+ if (fdef.defaultValue !== undefined) {
344
+ return false;
345
+ }
346
+ return true;
347
+ } catch (error) {
348
+ this.formLogger.debug(`Could not determine if field ${fieldPath} is required:`, error);
349
+ }
350
+ return false;
351
+ }
352
+
353
+ /**
354
+ * Override _call to intercept Zod validation errors at the execution level
355
+ */
356
+ override async _call(inputs: Record<string, unknown>): Promise<Record<string, unknown>> {
357
+ try {
358
+ const result = await super._call(inputs);
359
+
360
+ if (result.intermediateSteps && Array.isArray(result.intermediateSteps)) {
361
+ for (const step of result.intermediateSteps) {
362
+ if (step.observation) {
363
+ try {
364
+ const parsed: ToolResponse = typeof step.observation === 'string'
365
+ ? JSON.parse(step.observation)
366
+ : step.observation as ToolResponse;
367
+
368
+ if (parsed.requiresForm && parsed.formMessage) {
369
+ this.formLogger.info('Tool requested form generation', {
370
+ toolName: step.action?.tool,
371
+ hasForm: true
372
+ });
373
+
374
+ const actionToolName = (step as IntermediateStepData).action?.tool || 'unknown';
375
+ const toolInstance = this.tools.find(t => t.name === actionToolName) as ToolInterface | undefined;
376
+ const originalToolCandidate = (toolInstance as unknown as { originalTool?: { call?: (args: Record<string, unknown>) => Promise<string> } }) || {};
377
+ const pf: PendingFormData = {
378
+ toolName: actionToolName,
379
+ originalInput: inputs,
380
+ originalToolInput: (step as IntermediateStepData).action?.toolInput as Record<string, unknown> | undefined,
381
+ schema: null,
382
+ toolRef: toolInstance,
383
+ originalToolRef: originalToolCandidate?.originalTool
384
+ };
385
+ this.pendingForms.set(parsed.formMessage.id, pf);
386
+ globalPendingForms.set(parsed.formMessage.id, pf);
387
+
388
+ return {
389
+ ...result,
390
+ requiresForm: true,
391
+ formMessage: parsed.formMessage,
392
+ output: parsed.message || 'Please complete the form to continue.'
393
+ };
394
+ }
395
+
396
+ if (parsed.hashLinkBlock || (parsed.success && parsed.inscription && parsed.hashLinkBlock)) {
397
+ this.formLogger.info('Tool returned HashLink blocks', {
398
+ toolName: (step as IntermediateStepData).action?.tool,
399
+ hasHashLink: true,
400
+ blockId: parsed.hashLinkBlock?.blockId
401
+ });
402
+
403
+ const hashLinkResponse = this.processHashLinkResponse(parsed);
404
+
405
+ return {
406
+ ...result,
407
+ hasHashLinkBlocks: true,
408
+ hashLinkBlock: hashLinkResponse.hashLinkBlock,
409
+ output: hashLinkResponse.message
410
+ };
411
+ }
412
+ } catch {
413
+
414
+ }
415
+ }
416
+ }
417
+ }
418
+
419
+ return result;
420
+ } catch (error) {
421
+ if (error instanceof ZodError) {
422
+ this.formLogger.info('Intercepted ZodError during agent execution');
423
+ return this.handleValidationError(error, inputs, []);
424
+ }
425
+ throw error;
426
+ }
427
+ }
428
+
429
+ /**
430
+ * Handles Zod validation errors by generating forms
431
+ */
432
+ private async handleValidationError(
433
+ error: ZodError,
434
+ inputs: Record<string, unknown>,
435
+ intermediateSteps: AgentStep[]
436
+ ): Promise<Record<string, unknown>> {
437
+ this.formLogger.info('Zod validation error detected, generating form', {
438
+ errorIssues: error.issues.length,
439
+ inputKeys: Object.keys(inputs)
440
+ });
441
+
442
+ let toolInfo = this.extractToolInfoFromError(error, inputs, intermediateSteps);
443
+
444
+ if (!toolInfo) {
445
+ this.formLogger.warn('Could not extract tool info from validation error, trying fallback detection');
446
+ const fallbackTool = this.detectToolFromErrorContext(error);
447
+ if (!fallbackTool) {
448
+ this.formLogger.error('No tool detected for form generation, rethrowing error');
449
+ throw error;
450
+ }
451
+ toolInfo = fallbackTool;
452
+ }
453
+
454
+ this.formLogger.info('Generating form for tool:', {
455
+ toolName: toolInfo.toolName,
456
+ hasSchema: !!toolInfo.schema
457
+ });
458
+
459
+ const formMessage = this.formGenerator.generateFormFromError(
460
+ error,
461
+ toolInfo.schema as z.ZodType<unknown, z.ZodTypeDef, unknown>,
462
+ toolInfo.toolName,
463
+ (inputs.input as string) || ''
464
+ );
465
+
466
+ this.pendingForms.set(formMessage.id, {
467
+ toolName: toolInfo.toolName,
468
+ originalInput: inputs,
469
+ schema: toolInfo.schema
470
+ });
471
+
472
+ globalPendingForms.set(formMessage.id, {
473
+ toolName: toolInfo.toolName,
474
+ originalInput: inputs,
475
+ schema: toolInfo.schema
476
+ });
477
+
478
+ return {
479
+ output: this.formatFormResponse(formMessage),
480
+ formMessage,
481
+ requiresForm: true,
482
+ intermediateSteps: intermediateSteps || []
483
+ };
484
+ }
485
+
486
+ /**
487
+ * Get a copy of pending forms for preservation during executor recreation
488
+ */
489
+ getPendingForms(): Map<string, PendingFormData> {
490
+ return new Map(this.pendingForms);
491
+ }
492
+
493
+ /**
494
+ * Restore pending forms from a previous executor instance
495
+ */
496
+ restorePendingForms(forms: Map<string, PendingFormData>): void {
497
+ for (const [formId, formData] of forms) {
498
+ this.pendingForms.set(formId, formData);
499
+ }
500
+ }
501
+
502
+ /**
503
+ * Processes form submission and continues tool execution
504
+ */
505
+ async processFormSubmission(submission: FormSubmission): Promise<Record<string, unknown>> {
506
+ this.formLogger.info('🚀 FormAwareAgentExecutor.processFormSubmission called!', {
507
+ submissionFormId: submission.formId,
508
+ submissionToolName: submission.toolName
509
+ });
510
+ if (!submission) {
511
+ throw new Error('Form submission is null or undefined');
512
+ }
513
+
514
+ if (!submission.formId) {
515
+ throw new Error('Form submission missing formId');
516
+ }
517
+
518
+ if (!submission.parameters || submission.parameters === null || typeof submission.parameters !== 'object' || Array.isArray(submission.parameters)) {
519
+ throw new Error(`Form submission parameters are invalid: ${typeof submission.parameters}, isNull: ${submission.parameters === null}, isArray: ${Array.isArray(submission.parameters)}, parameters: ${JSON.stringify(submission.parameters)}`);
520
+ }
521
+
522
+ this.formLogger.info('Processing form submission:', {
523
+ formId: submission.formId,
524
+ toolName: submission.toolName,
525
+ parameterKeys: Object.keys(submission.parameters),
526
+ parametersType: typeof submission.parameters,
527
+ parametersIsNull: submission.parameters === null,
528
+ parametersIsUndefined: submission.parameters === undefined,
529
+ hasContext: !!submission.context,
530
+ });
531
+
532
+ let pendingForm = this.pendingForms.get(submission.formId);
533
+
534
+ if (!pendingForm) {
535
+ pendingForm = globalPendingForms.get(submission.formId);
536
+ if (!pendingForm) {
537
+ throw new Error(`No pending form found for ID: ${submission.formId}`);
538
+ }
539
+ }
540
+
541
+ this.pendingForms.delete(submission.formId);
542
+ globalPendingForms.delete(submission.formId);
543
+
544
+ const tool = pendingForm.toolRef || this.tools.find(t => t.name === pendingForm.toolName);
545
+ if (!tool) {
546
+ throw new Error(`Tool not found for form submission: ${pendingForm.toolName}`);
547
+ }
548
+
549
+ let baseToolInput: Record<string, unknown> = {};
550
+ try {
551
+ if (pendingForm.originalToolInput && typeof pendingForm.originalToolInput === 'object') {
552
+ baseToolInput = { ...(pendingForm.originalToolInput as Record<string, unknown>) };
553
+ }
554
+ } catch (error) {
555
+ this.formLogger.warn('Failed to extract base tool input, using empty object:', error);
556
+ baseToolInput = {};
557
+ }
558
+
559
+ let submissionData: Record<string, unknown> = {};
560
+ try {
561
+ if (submission.parameters && typeof submission.parameters === 'object') {
562
+ submissionData = { ...(submission.parameters as Record<string, unknown>) };
563
+ }
564
+ } catch (error) {
565
+ this.formLogger.warn('Failed to extract submission parameters, using empty object:', error);
566
+ submissionData = {};
567
+ }
568
+
569
+ const mergedToolInput: Record<string, unknown> = {};
570
+ try {
571
+ Object.keys(baseToolInput).forEach(key => {
572
+ const value = baseToolInput[key];
573
+ if (value !== undefined && value !== null) {
574
+ mergedToolInput[key] = value;
575
+ }
576
+ });
577
+
578
+ Object.keys(submissionData).forEach(key => {
579
+ const value = submissionData[key];
580
+ if (value !== undefined && value !== null) {
581
+ mergedToolInput[key] = value;
582
+ }
583
+ });
584
+
585
+ mergedToolInput.renderForm = false;
586
+ mergedToolInput.__fromForm = true;
587
+
588
+
589
+ this.formLogger.info('Successfully merged tool input:', {
590
+ baseKeys: Object.keys(baseToolInput),
591
+ submissionKeys: Object.keys(submissionData),
592
+ mergedKeys: Object.keys(mergedToolInput),
593
+ });
594
+ } catch (error) {
595
+ this.formLogger.error('Failed to merge tool input data:', error);
596
+ throw new Error(`Failed to merge tool input data: ${error instanceof Error ? error.message : 'Unknown error'}`);
597
+ }
598
+
599
+ try {
600
+ const maybeWrapper = tool as unknown as {
601
+ executeOriginal?: (args: Record<string, unknown>) => Promise<string>;
602
+ getOriginalTool?: () => { _call?: (args: Record<string, unknown>) => Promise<string>; call?: (args: Record<string, unknown>) => Promise<string> };
603
+ originalTool?: { _call?: (args: Record<string, unknown>) => Promise<string>; call?: (args: Record<string, unknown>) => Promise<string> };
604
+ call?: (args: Record<string, unknown>) => Promise<string>;
605
+ };
606
+ let toolOutput: string;
607
+ if (typeof maybeWrapper.executeOriginal === 'function') {
608
+ toolOutput = await maybeWrapper.executeOriginal(mergedToolInput);
609
+ } else if (typeof maybeWrapper.getOriginalTool === 'function') {
610
+ const ot = maybeWrapper.getOriginalTool();
611
+ const otCall = ot as unknown as {
612
+ _call?: (a: Record<string, unknown>) => Promise<string>;
613
+ call?: (a: Record<string, unknown>) => Promise<string>;
614
+ };
615
+ if (ot && typeof otCall._call === 'function') {
616
+ toolOutput = await otCall._call(mergedToolInput);
617
+ } else if (ot && typeof otCall.call === 'function') {
618
+ toolOutput = await otCall.call(mergedToolInput);
619
+ } else {
620
+ const tcall = tool as unknown as { call?: (a: Record<string, unknown>) => Promise<string> };
621
+ if (typeof tcall.call === 'function') {
622
+ toolOutput = await tcall.call(mergedToolInput);
623
+ } else {
624
+ throw new Error('No callable tool implementation found for form submission');
625
+ }
626
+ }
627
+ } else if (maybeWrapper.originalTool && typeof maybeWrapper.originalTool._call === 'function') {
628
+ toolOutput = await maybeWrapper.originalTool._call(mergedToolInput);
629
+ } else if (maybeWrapper.originalTool && typeof maybeWrapper.originalTool.call === 'function') {
630
+ toolOutput = await maybeWrapper.originalTool.call(mergedToolInput);
631
+ } else if (typeof (tool as unknown as { call?: (a: Record<string, unknown>) => Promise<string> }).call === 'function') {
632
+ toolOutput = await (tool as unknown as { call: (a: Record<string, unknown>) => Promise<string> }).call(mergedToolInput);
633
+ } else {
634
+ throw new Error('No callable tool implementation found for form submission');
635
+ }
636
+
637
+ let responseMetadata: Record<string, unknown> = {};
638
+ let formattedOutput: string;
639
+
640
+ this.formLogger.info('🔍 METADATA EXTRACTION: Analyzing tool output', {
641
+ outputType: typeof toolOutput,
642
+ outputLength: toolOutput?.length || 0,
643
+ isString: typeof toolOutput === 'string',
644
+ firstChars: typeof toolOutput === 'string' ? toolOutput.substring(0, 100) : 'not-string'
645
+ });
646
+
647
+ try {
648
+ const parsed = JSON.parse(toolOutput);
649
+ this.formLogger.info('✅ METADATA EXTRACTION: Successfully parsed JSON', {
650
+ jsonKeys: Object.keys(parsed),
651
+ hasHashLinkBlock: !!(parsed as Record<string, unknown>).hashLinkBlock
652
+ });
653
+
654
+ if (parsed && typeof parsed === 'object') {
655
+ if (ResponseFormatter.isHashLinkResponse(parsed)) {
656
+ this.formLogger.info('🔗 HASHLINK DETECTED: Processing HashLink response separately to preserve metadata');
657
+
658
+ responseMetadata = {
659
+ ...responseMetadata,
660
+ hashLinkBlock: (parsed as Record<string, unknown>).hashLinkBlock
661
+ };
662
+
663
+ formattedOutput = ResponseFormatter.formatHashLinkResponse(parsed);
664
+
665
+ this.formLogger.info('🔗 METADATA PRESERVED: HashLink metadata extracted for component rendering', {
666
+ blockId: (responseMetadata.hashLinkBlock as any)?.blockId,
667
+ hasTemplate: !!(responseMetadata.hashLinkBlock as any)?.template
668
+ });
669
+ } else {
670
+ formattedOutput = ResponseFormatter.formatResponse(toolOutput);
671
+
672
+ responseMetadata = {
673
+ ...responseMetadata,
674
+ hashLinkBlock: (parsed as Record<string, unknown>).hashLinkBlock
675
+ };
676
+ }
677
+ } else {
678
+ formattedOutput = ResponseFormatter.formatResponse(toolOutput);
679
+ }
680
+ } catch (error) {
681
+ this.formLogger.warn('❌ METADATA EXTRACTION: Tool output is not JSON', {
682
+ error: error instanceof Error ? error.message : 'unknown error',
683
+ outputSample: typeof toolOutput === 'string' ? toolOutput.substring(0, 200) : 'not-string'
684
+ });
685
+
686
+ formattedOutput = ResponseFormatter.formatResponse(toolOutput);
687
+ }
688
+
689
+ return {
690
+ output: formattedOutput,
691
+ formCompleted: true,
692
+ originalFormId: submission.formId,
693
+ intermediateSteps: [],
694
+ metadata: responseMetadata
695
+ };
696
+ } catch (error) {
697
+ if (error instanceof ZodError) {
698
+ return this.handleValidationError(error, mergedToolInput, []);
699
+ }
700
+ throw error;
701
+ }
702
+ }
703
+
704
+ /**
705
+ * Extracts tool information from the execution context
706
+ */
707
+ private extractToolInfoFromError(
708
+ error: ZodError,
709
+ inputs: Record<string, unknown>,
710
+ intermediateSteps: AgentStep[]
711
+ ): { toolName: string; schema: unknown } | null {
712
+ try {
713
+ if (intermediateSteps.length > 0) {
714
+ const lastStep = intermediateSteps[intermediateSteps.length - 1];
715
+ if (lastStep.action && lastStep.action.tool) {
716
+ const tool = this.tools.find(t => t.name === lastStep.action.tool);
717
+ if (tool && 'schema' in tool) {
718
+ this.formLogger.info('Found tool from intermediate steps:', lastStep.action.tool);
719
+ return {
720
+ toolName: lastStep.action.tool,
721
+ schema: (tool as unknown as Record<string, unknown>).schema
722
+ };
723
+ }
724
+ }
725
+ }
726
+
727
+ const inputSteps = (inputs.intermediateSteps as unknown[]) || [];
728
+ if (inputSteps.length > 0) {
729
+ const lastStep = inputSteps[inputSteps.length - 1];
730
+ let action: AgentAction;
731
+
732
+ if (Array.isArray(lastStep) && lastStep.length > 0) {
733
+ action = lastStep[0] as AgentAction;
734
+ } else if ((lastStep as Record<string, unknown>).action) {
735
+ action = (lastStep as Record<string, unknown>).action as AgentAction;
736
+ } else {
737
+ action = lastStep as AgentAction;
738
+ }
739
+
740
+ if (action && action.tool) {
741
+ const tool = this.tools.find(t => t.name === action.tool);
742
+ if (tool && 'schema' in tool) {
743
+ this.formLogger.info('Found tool from input steps:', action.tool);
744
+ return {
745
+ toolName: action.tool,
746
+ schema: (tool as unknown as Record<string, unknown>).schema
747
+ };
748
+ }
749
+ }
750
+ }
751
+
752
+ const toolFromContext = this.findToolFromContext(inputs);
753
+ if (toolFromContext) {
754
+ this.formLogger.info('Found tool from context:', toolFromContext.toolName);
755
+ return toolFromContext;
756
+ }
757
+
758
+ return null;
759
+ } catch (err) {
760
+ this.formLogger.error('Error extracting tool info:', err);
761
+ return null;
762
+ }
763
+ }
764
+
765
+ /**
766
+ * Attempts to find tool from execution context
767
+ */
768
+ private findToolFromContext(inputs: Record<string, unknown>): {
769
+ toolName: string;
770
+ schema: unknown
771
+ } | null {
772
+ const inputText = (inputs.input as string) || '';
773
+
774
+ for (const tool of this.tools) {
775
+ const keywords = this.extractToolKeywords(tool.name);
776
+
777
+ if (keywords.some(keyword =>
778
+ inputText.toLowerCase().includes(keyword.toLowerCase())
779
+ )) {
780
+ if ('schema' in tool) {
781
+ return {
782
+ toolName: tool.name,
783
+ schema: (tool as unknown as Record<string, unknown>).schema
784
+ };
785
+ }
786
+ }
787
+ }
788
+
789
+ return null;
790
+ }
791
+
792
+ /**
793
+ * Additional fallback to detect tool from error context
794
+ */
795
+ private detectToolFromErrorContext(
796
+ error: ZodError
797
+ ): { toolName: string; schema: unknown } | null {
798
+ const errorPaths = error.issues.map(issue => issue.path.join('.'));
799
+
800
+ for (const tool of this.tools) {
801
+ if ('schema' in tool) {
802
+ const toolSchema = (tool as unknown as Record<string, unknown>).schema;
803
+ if (this.schemaMatchesErrorPaths(toolSchema, errorPaths)) {
804
+ this.formLogger.info('Detected tool from error path analysis:', tool.name);
805
+ return {
806
+ toolName: tool.name,
807
+ schema: toolSchema
808
+ };
809
+ }
810
+ }
811
+ }
812
+
813
+ return null;
814
+ }
815
+
816
+ /**
817
+ * Checks if a schema structure matches error paths
818
+ */
819
+ private schemaMatchesErrorPaths(schema: unknown, errorPaths: string[]): boolean {
820
+ const schemaRecord = schema as Record<string, unknown>;
821
+ if (!schemaRecord || !schemaRecord._def) return false;
822
+
823
+ try {
824
+ const def = schemaRecord._def as Record<string, unknown>;
825
+ if (def.typeName === 'ZodObject') {
826
+ const shape = def.shape as Record<string, unknown>;
827
+ const schemaKeys = Object.keys(shape || {});
828
+ return errorPaths.some(path => {
829
+ const topLevelKey = path.split('.')[0];
830
+ return schemaKeys.includes(topLevelKey);
831
+ });
832
+ }
833
+ } catch (err) {
834
+ this.formLogger.debug('Error analyzing schema structure:', err);
835
+ }
836
+
837
+ return false;
838
+ }
839
+
840
+ /**
841
+ * Extracts keywords from tool name for matching
842
+ */
843
+ private extractToolKeywords(toolName: string): string[] {
844
+ const words = toolName
845
+ .replace(/([A-Z])/g, ' $1')
846
+ .toLowerCase()
847
+ .split(/[\s_-]+/)
848
+ .filter(w => w.length > 2);
849
+
850
+ return [...words, toolName.toLowerCase()];
851
+ }
852
+
853
+ /**
854
+ * Formats the form message for display
855
+ */
856
+ private formatFormResponse(formMessage: FormMessage): string {
857
+ const fieldCount = formMessage.formConfig.fields.length;
858
+ const fieldList = formMessage.formConfig.fields
859
+ .slice(0, 3)
860
+ .map(f => `• ${f.label}`)
861
+ .join('\n');
862
+
863
+ return `I need some additional information to complete your request.
864
+
865
+ ${formMessage.formConfig.description}
866
+
867
+ Required fields:
868
+ ${fieldList}${fieldCount > 3 ? `\n... and ${fieldCount - 3} more` : ''}
869
+
870
+ Please fill out the form below to continue.`;
871
+ }
872
+
873
+ /**
874
+ * Check if there are pending forms
875
+ */
876
+ hasPendingForms(): boolean {
877
+ return this.pendingForms.size > 0;
878
+ }
879
+
880
+ /**
881
+ * Get information about pending forms
882
+ */
883
+ getPendingFormsInfo(): Array<{ formId: string; toolName: string }> {
884
+ return Array.from(this.pendingForms.entries()).map(([formId, info]) => ({
885
+ formId,
886
+ toolName: info.toolName
887
+ }));
888
+ }
889
+
890
+ /**
891
+ * Processes HashLink block responses from tools
892
+ */
893
+ private processHashLinkResponse(toolResponse: ToolResponse): HashLinkResponse {
894
+ try {
895
+
896
+ let hashLinkBlock: HashLinkBlock | undefined;
897
+
898
+ if (toolResponse.hashLinkBlock) {
899
+ hashLinkBlock = toolResponse.hashLinkBlock;
900
+ } else if (toolResponse.success && toolResponse.inscription && toolResponse.hashLinkBlock) {
901
+ hashLinkBlock = toolResponse.hashLinkBlock;
902
+ }
903
+
904
+ if (!hashLinkBlock) {
905
+ throw new Error('HashLink block data not found in response');
906
+ }
907
+
908
+ if (!hashLinkBlock.blockId || !hashLinkBlock.hashLink || !hashLinkBlock.attributes) {
909
+ throw new Error('Invalid HashLink block structure - missing required fields');
910
+ }
911
+
912
+ let message = 'Content processed successfully!';
913
+
914
+ if (toolResponse.success && toolResponse.inscription) {
915
+ const inscription = toolResponse.inscription;
916
+ const metadata = toolResponse.metadata || {};
917
+
918
+ message = `✅ ${inscription.standard} Hashinal inscription completed!\n\n`;
919
+
920
+ if (metadata.name) {
921
+ message += `**${metadata.name}**\n`;
922
+ }
923
+
924
+ if (metadata.description) {
925
+ message += `${metadata.description}\n\n`;
926
+ }
927
+
928
+ message += `📍 **Topic ID:** ${inscription.topicId}\n`;
929
+ message += `🔗 **HRL:** ${inscription.hrl}\n`;
930
+
931
+ if (inscription.cdnUrl) {
932
+ message += `🌐 **CDN URL:** ${inscription.cdnUrl}\n`;
933
+ }
934
+
935
+ if (metadata.creator) {
936
+ message += `👤 **Creator:** ${metadata.creator}\n`;
937
+ }
938
+ }
939
+
940
+ this.formLogger.info('Processed HashLink response', {
941
+ blockId: hashLinkBlock.blockId,
942
+ hashLink: hashLinkBlock.hashLink,
943
+ hasTemplate: !!hashLinkBlock.template,
944
+ attributeCount: Object.keys(hashLinkBlock.attributes || {}).length
945
+ });
946
+
947
+ return {
948
+ hasHashLinkBlocks: true,
949
+ hashLinkBlock,
950
+ message
951
+ };
952
+ } catch (error) {
953
+ this.formLogger.error('Error processing HashLink response:', error);
954
+
955
+ return {
956
+ hasHashLinkBlocks: false,
957
+ message: 'Content processed, but interactive display is not available.'
958
+ };
959
+ }
960
+ }
961
+
962
+ /**
963
+ * Get FormEngine statistics for debugging
964
+ */
965
+ getFormEngineStatistics() {
966
+ return {
967
+ strategies: this.formEngine.getRegisteredStrategies(),
968
+ middleware: this.formEngine.getRegisteredMiddleware(),
969
+ };
970
+ }
971
+ }