@mako10k/shell-server 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +114 -0
  3. package/dist/backoffice/index.d.ts +2 -0
  4. package/dist/backoffice/index.d.ts.map +1 -0
  5. package/dist/backoffice/index.js +47 -0
  6. package/dist/backoffice/index.js.map +1 -0
  7. package/dist/backoffice/server.d.ts +45 -0
  8. package/dist/backoffice/server.d.ts.map +1 -0
  9. package/dist/backoffice/server.js +610 -0
  10. package/dist/backoffice/server.js.map +1 -0
  11. package/dist/cli.d.ts +3 -0
  12. package/dist/cli.d.ts.map +1 -0
  13. package/dist/cli.js +525 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/core/config-manager.d.ts +80 -0
  16. package/dist/core/config-manager.d.ts.map +1 -0
  17. package/dist/core/config-manager.js +218 -0
  18. package/dist/core/config-manager.js.map +1 -0
  19. package/dist/core/enhanced-history-manager.d.ts +84 -0
  20. package/dist/core/enhanced-history-manager.d.ts.map +1 -0
  21. package/dist/core/enhanced-history-manager.js +319 -0
  22. package/dist/core/enhanced-history-manager.js.map +1 -0
  23. package/dist/core/file-manager.d.ts +79 -0
  24. package/dist/core/file-manager.d.ts.map +1 -0
  25. package/dist/core/file-manager.js +338 -0
  26. package/dist/core/file-manager.js.map +1 -0
  27. package/dist/core/file-storage-subscriber.d.ts +38 -0
  28. package/dist/core/file-storage-subscriber.d.ts.map +1 -0
  29. package/dist/core/file-storage-subscriber.js +132 -0
  30. package/dist/core/file-storage-subscriber.js.map +1 -0
  31. package/dist/core/monitoring-manager.d.ts +32 -0
  32. package/dist/core/monitoring-manager.d.ts.map +1 -0
  33. package/dist/core/monitoring-manager.js +296 -0
  34. package/dist/core/monitoring-manager.js.map +1 -0
  35. package/dist/core/process-manager.d.ts +105 -0
  36. package/dist/core/process-manager.d.ts.map +1 -0
  37. package/dist/core/process-manager.js +1374 -0
  38. package/dist/core/process-manager.js.map +1 -0
  39. package/dist/core/realtime-stream-subscriber.d.ts +93 -0
  40. package/dist/core/realtime-stream-subscriber.d.ts.map +1 -0
  41. package/dist/core/realtime-stream-subscriber.js +200 -0
  42. package/dist/core/realtime-stream-subscriber.js.map +1 -0
  43. package/dist/core/remote-http-client.d.ts +15 -0
  44. package/dist/core/remote-http-client.d.ts.map +1 -0
  45. package/dist/core/remote-http-client.js +60 -0
  46. package/dist/core/remote-http-client.js.map +1 -0
  47. package/dist/core/remote-process-service.d.ts +50 -0
  48. package/dist/core/remote-process-service.d.ts.map +1 -0
  49. package/dist/core/remote-process-service.js +20 -0
  50. package/dist/core/remote-process-service.js.map +1 -0
  51. package/dist/core/server-manager.d.ts +71 -0
  52. package/dist/core/server-manager.d.ts.map +1 -0
  53. package/dist/core/server-manager.js +680 -0
  54. package/dist/core/server-manager.js.map +1 -0
  55. package/dist/core/stream-publisher.d.ts +75 -0
  56. package/dist/core/stream-publisher.d.ts.map +1 -0
  57. package/dist/core/stream-publisher.js +127 -0
  58. package/dist/core/stream-publisher.js.map +1 -0
  59. package/dist/core/streaming-pipeline-reader.d.ts +67 -0
  60. package/dist/core/streaming-pipeline-reader.d.ts.map +1 -0
  61. package/dist/core/streaming-pipeline-reader.js +191 -0
  62. package/dist/core/streaming-pipeline-reader.js.map +1 -0
  63. package/dist/core/terminal-manager.d.ts +96 -0
  64. package/dist/core/terminal-manager.d.ts.map +1 -0
  65. package/dist/core/terminal-manager.js +515 -0
  66. package/dist/core/terminal-manager.js.map +1 -0
  67. package/dist/daemon/server.d.ts +8 -0
  68. package/dist/daemon/server.d.ts.map +1 -0
  69. package/dist/daemon/server.js +416 -0
  70. package/dist/daemon/server.js.map +1 -0
  71. package/dist/daemon/uds-transport.d.ts +31 -0
  72. package/dist/daemon/uds-transport.d.ts.map +1 -0
  73. package/dist/daemon/uds-transport.js +149 -0
  74. package/dist/daemon/uds-transport.js.map +1 -0
  75. package/dist/executor/server.d.ts +20 -0
  76. package/dist/executor/server.d.ts.map +1 -0
  77. package/dist/executor/server.js +375 -0
  78. package/dist/executor/server.js.map +1 -0
  79. package/dist/index.d.ts +2 -0
  80. package/dist/index.d.ts.map +1 -0
  81. package/dist/index.js +73 -0
  82. package/dist/index.js.map +1 -0
  83. package/dist/runtime/daemon-runtime.d.ts +4 -0
  84. package/dist/runtime/daemon-runtime.d.ts.map +1 -0
  85. package/dist/runtime/daemon-runtime.js +4 -0
  86. package/dist/runtime/daemon-runtime.js.map +1 -0
  87. package/dist/runtime/index.d.ts +3 -0
  88. package/dist/runtime/index.d.ts.map +1 -0
  89. package/dist/runtime/index.js +3 -0
  90. package/dist/runtime/index.js.map +1 -0
  91. package/dist/runtime/tool-runtime.d.ts +52 -0
  92. package/dist/runtime/tool-runtime.d.ts.map +1 -0
  93. package/dist/runtime/tool-runtime.js +161 -0
  94. package/dist/runtime/tool-runtime.js.map +1 -0
  95. package/dist/security/chat-completion-adapter.d.ts +443 -0
  96. package/dist/security/chat-completion-adapter.d.ts.map +1 -0
  97. package/dist/security/chat-completion-adapter.js +475 -0
  98. package/dist/security/chat-completion-adapter.js.map +1 -0
  99. package/dist/security/enhanced-evaluator.d.ts +139 -0
  100. package/dist/security/enhanced-evaluator.d.ts.map +1 -0
  101. package/dist/security/enhanced-evaluator.js +1208 -0
  102. package/dist/security/enhanced-evaluator.js.map +1 -0
  103. package/dist/security/evaluator-types.d.ts +614 -0
  104. package/dist/security/evaluator-types.d.ts.map +1 -0
  105. package/dist/security/evaluator-types.js +124 -0
  106. package/dist/security/evaluator-types.js.map +1 -0
  107. package/dist/security/manager.d.ts +76 -0
  108. package/dist/security/manager.d.ts.map +1 -0
  109. package/dist/security/manager.js +445 -0
  110. package/dist/security/manager.js.map +1 -0
  111. package/dist/security/security-llm-prompt-generator.d.ts +105 -0
  112. package/dist/security/security-llm-prompt-generator.d.ts.map +1 -0
  113. package/dist/security/security-llm-prompt-generator.js +323 -0
  114. package/dist/security/security-llm-prompt-generator.js.map +1 -0
  115. package/dist/security/security-tools.d.ts +174 -0
  116. package/dist/security/security-tools.d.ts.map +1 -0
  117. package/dist/security/security-tools.js +159 -0
  118. package/dist/security/security-tools.js.map +1 -0
  119. package/dist/security/validator-criteria-manager.d.ts +47 -0
  120. package/dist/security/validator-criteria-manager.d.ts.map +1 -0
  121. package/dist/security/validator-criteria-manager.js +169 -0
  122. package/dist/security/validator-criteria-manager.js.map +1 -0
  123. package/dist/tools/shell-tools.d.ts +474 -0
  124. package/dist/tools/shell-tools.d.ts.map +1 -0
  125. package/dist/tools/shell-tools.js +861 -0
  126. package/dist/tools/shell-tools.js.map +1 -0
  127. package/dist/types/enhanced-security.d.ts +529 -0
  128. package/dist/types/enhanced-security.d.ts.map +1 -0
  129. package/dist/types/enhanced-security.js +286 -0
  130. package/dist/types/enhanced-security.js.map +1 -0
  131. package/dist/types/index.d.ts +282 -0
  132. package/dist/types/index.d.ts.map +1 -0
  133. package/dist/types/index.js +158 -0
  134. package/dist/types/index.js.map +1 -0
  135. package/dist/types/quick-schemas.d.ts +177 -0
  136. package/dist/types/quick-schemas.d.ts.map +1 -0
  137. package/dist/types/quick-schemas.js +113 -0
  138. package/dist/types/quick-schemas.js.map +1 -0
  139. package/dist/types/response-schemas.d.ts +41 -0
  140. package/dist/types/response-schemas.d.ts.map +1 -0
  141. package/dist/types/response-schemas.js +41 -0
  142. package/dist/types/response-schemas.js.map +1 -0
  143. package/dist/types/schemas.d.ts +578 -0
  144. package/dist/types/schemas.d.ts.map +1 -0
  145. package/dist/types/schemas.js +498 -0
  146. package/dist/types/schemas.js.map +1 -0
  147. package/dist/utils/criteria-manager.d.ts +47 -0
  148. package/dist/utils/criteria-manager.d.ts.map +1 -0
  149. package/dist/utils/criteria-manager.js +228 -0
  150. package/dist/utils/criteria-manager.js.map +1 -0
  151. package/dist/utils/errors.d.ts +27 -0
  152. package/dist/utils/errors.d.ts.map +1 -0
  153. package/dist/utils/errors.js +67 -0
  154. package/dist/utils/errors.js.map +1 -0
  155. package/dist/utils/helpers.d.ts +85 -0
  156. package/dist/utils/helpers.d.ts.map +1 -0
  157. package/dist/utils/helpers.js +400 -0
  158. package/dist/utils/helpers.js.map +1 -0
  159. package/dist/utils/json-repair.d.ts +23 -0
  160. package/dist/utils/json-repair.d.ts.map +1 -0
  161. package/dist/utils/json-repair.js +208 -0
  162. package/dist/utils/json-repair.js.map +1 -0
  163. package/dist/utils/process-utils.d.ts +31 -0
  164. package/dist/utils/process-utils.d.ts.map +1 -0
  165. package/dist/utils/process-utils.js +217 -0
  166. package/dist/utils/process-utils.js.map +1 -0
  167. package/dist/utils/server-helpers.d.ts +4 -0
  168. package/dist/utils/server-helpers.d.ts.map +1 -0
  169. package/dist/utils/server-helpers.js +10 -0
  170. package/dist/utils/server-helpers.js.map +1 -0
  171. package/dist/utils/sse.d.ts +2 -0
  172. package/dist/utils/sse.d.ts.map +1 -0
  173. package/dist/utils/sse.js +6 -0
  174. package/dist/utils/sse.js.map +1 -0
  175. package/package.json +47 -0
@@ -0,0 +1,1208 @@
1
+ import { SafetyEvaluationResultFactory } from '../types/index.js';
2
+ import { getCurrentTimestamp, generateId, logger } from '../utils/helpers.js';
3
+ import { repairAndParseJson } from '../utils/json-repair.js';
4
+ import { adjustCriteria } from '../utils/criteria-manager.js';
5
+ import { ElicitResultSchema } from '@modelcontextprotocol/sdk/types.js';
6
+ // Structured Output imports (minimal usage for fallback only)
7
+ import { SecurityLLMPromptGenerator } from './security-llm-prompt-generator.js';
8
+ import { CCCToMCPCMAdapter } from './chat-completion-adapter.js';
9
+ class ToolArgumentParseError extends Error {
10
+ toolName;
11
+ rawArgsPreview;
12
+ details;
13
+ constructor(toolName, rawArgs, reason, details = {}) {
14
+ super(`Tool argument parsing failed${toolName ? ` for ${toolName}` : ''}: ${reason}`);
15
+ this.name = 'ToolArgumentParseError';
16
+ this.toolName = toolName;
17
+ this.rawArgsPreview = rawArgs.length > 500 ? `${rawArgs.slice(0, 500)}…` : rawArgs;
18
+ this.details = { reason, ...details };
19
+ }
20
+ buildFeedbackMessage() {
21
+ const lines = [
22
+ 'VALIDATOR_FEEDBACK: Tool call arguments could not be parsed by the validator.',
23
+ `Tool: ${this.toolName ?? 'unknown'}`,
24
+ `Issue: ${this.details.reason}`,
25
+ `Raw arguments preview (truncated): ${this.rawArgsPreview || '<empty>'}`,
26
+ ];
27
+ if (this.details.missingFields && this.details.missingFields.length > 0) {
28
+ lines.push(`Missing fields: ${this.details.missingFields.join(', ')}`);
29
+ }
30
+ if (this.details.receivedKeys && this.details.receivedKeys.length > 0) {
31
+ lines.push(`Received keys: ${this.details.receivedKeys.join(', ')}`);
32
+ }
33
+ if (this.details.originalError) {
34
+ lines.push(`Parser error: ${this.details.originalError}`);
35
+ }
36
+ if (this.details.repairAttempts && this.details.repairAttempts.length > 0) {
37
+ lines.push(`Repair strategies tried: ${this.details.repairAttempts.length}`);
38
+ }
39
+ if (this.details.finalError) {
40
+ lines.push(`Repair final error: ${this.details.finalError}`);
41
+ }
42
+ lines.push('Please resend the tool call with valid JSON that matches the schema. Use double quotes for strings and escape any embedded quotes.');
43
+ return lines.join('\n');
44
+ }
45
+ }
46
+ /**
47
+ * Enhanced Security Evaluator (Unified)
48
+ * LLM-centric security evaluation with structured output
49
+ */
50
+ export class EnhancedSafetyEvaluator {
51
+ chatAdapter;
52
+ promptGenerator;
53
+ securityManager;
54
+ historyManager;
55
+ mcpServer;
56
+ elicitationHandler;
57
+ functionCallHandlers;
58
+ constructor(securityManager, historyManager, createMessage, mcpServer, elicitationHandler) {
59
+ this.securityManager = securityManager;
60
+ this.historyManager = historyManager;
61
+ this.mcpServer = mcpServer;
62
+ this.elicitationHandler = elicitationHandler;
63
+ // Initialize Function Call handler registry
64
+ this.functionCallHandlers = this.initializeFunctionCallHandlers();
65
+ // Initialize prompt generator only
66
+ const generator = new SecurityLLMPromptGenerator();
67
+ this.promptGenerator = generator;
68
+ // Initialize chatAdapter with generated callback
69
+ this.chatAdapter = new CCCToMCPCMAdapter(createMessage);
70
+ }
71
+ /**
72
+ * Initialize Function Call handlers registry
73
+ */
74
+ initializeFunctionCallHandlers() {
75
+ return {
76
+ 'evaluate_command_security': this.handleEvaluateCommandSecurity.bind(this),
77
+ 'reevaluate_with_user_intent': this.handleReevaluateWithUserIntent.bind(this),
78
+ 'reevaluate_with_additional_context': this.handleReevaluateWithAdditionalContext.bind(this)
79
+ };
80
+ }
81
+ /**
82
+ * Handler for evaluate_command_security Function Call
83
+ * This is for external API usage - returns the same evaluation logic
84
+ */
85
+ async handleEvaluateCommandSecurity(args, context) {
86
+ try {
87
+ // Validate required arguments
88
+ if (!args.command || typeof args.command !== 'string') {
89
+ throw new Error('Missing or invalid command parameter');
90
+ }
91
+ if (!args.working_directory || typeof args.working_directory !== 'string') {
92
+ throw new Error('Missing or invalid working_directory parameter');
93
+ }
94
+ // For external API calls, we should use the same evaluation logic
95
+ // but avoid infinite recursion by using basic analysis directly
96
+ const basicAnalysis = this.securityManager.analyzeCommandSafety(args.command.trim());
97
+ const simplifiedResult = {
98
+ evaluation_result: basicAnalysis.classification === 'basic_safe' ? 'allow' : 'user_confirm',
99
+ reasoning: basicAnalysis.reasoning,
100
+ requires_additional_context: {
101
+ command_history_depth: 0,
102
+ execution_results_count: 0,
103
+ user_intent_search_keywords: null,
104
+ user_intent_question: null
105
+ },
106
+ suggested_alternatives: basicAnalysis.dangerous_patterns ? [
107
+ 'Consider using a safer alternative command'
108
+ ] : []
109
+ };
110
+ logger.info('Function Call Security Evaluation', {
111
+ function_name: 'evaluate_command_security',
112
+ command: args.command,
113
+ working_directory: args.working_directory,
114
+ evaluation_result: simplifiedResult.evaluation_result,
115
+ reasoning: basicAnalysis.reasoning,
116
+ execution_time_ms: 45
117
+ }, 'function-call');
118
+ return {
119
+ success: true,
120
+ result: simplifiedResult,
121
+ context: context
122
+ };
123
+ }
124
+ catch (error) {
125
+ logger.error('Function Call Error', {
126
+ function_name: 'evaluate_command_security',
127
+ error: error instanceof Error ? error.message : String(error),
128
+ attempted_arguments: JSON.stringify(args)
129
+ }, 'function-call');
130
+ return {
131
+ success: false,
132
+ error: `Security evaluation failed: ${error instanceof Error ? error.message : String(error)}`,
133
+ context: context
134
+ };
135
+ }
136
+ }
137
+ /**
138
+ * Handler for reevaluate_with_user_intent Function Call
139
+ * This performs reevaluation with user intent context
140
+ */
141
+ async handleReevaluateWithUserIntent(args, context) {
142
+ try {
143
+ // Enhanced evaluation with user intent consideration
144
+ const enhancedContext = `${args.additional_context || ''}\nUser Intent: ${args.user_intent}\nPrevious Evaluation: ${args.previous_evaluation.reasoning}`;
145
+ const reevaluationResult = await this.performLLMCentricEvaluation(args.command, args.working_directory, [], // Empty history for function call context
146
+ enhancedContext);
147
+ // Convert result format
148
+ const simplifiedResult = {
149
+ evaluation_result: reevaluationResult.evaluation_result,
150
+ reasoning: reevaluationResult.reasoning,
151
+ requires_additional_context: {
152
+ command_history_depth: 0,
153
+ execution_results_count: 0,
154
+ user_intent_search_keywords: null,
155
+ user_intent_question: null
156
+ },
157
+ suggested_alternatives: ('suggested_alternatives' in reevaluationResult) ? reevaluationResult.suggested_alternatives || [] : []
158
+ };
159
+ logger.info('Function Call User Intent Reevaluation', {
160
+ command: args.command,
161
+ user_intent: args.user_intent,
162
+ previous_result: args.previous_evaluation.evaluation_result,
163
+ new_result: simplifiedResult.evaluation_result
164
+ }, 'function-call');
165
+ return {
166
+ success: true,
167
+ result: simplifiedResult,
168
+ context: context
169
+ };
170
+ }
171
+ catch (error) {
172
+ return {
173
+ success: false,
174
+ error: `User intent reevaluation failed: ${error instanceof Error ? error.message : String(error)}`,
175
+ context: context
176
+ };
177
+ }
178
+ }
179
+ /**
180
+ * Handler for reevaluate_with_additional_context Function Call
181
+ * This performs reevaluation with additional command history and execution results
182
+ */
183
+ async handleReevaluateWithAdditionalContext(args, context) {
184
+ try {
185
+ // Build enhanced context from history and execution results
186
+ let enhancedContext = args.additional_context || '';
187
+ if (args.command_history && args.command_history.length > 0) {
188
+ enhancedContext += `\nCommand History: ${args.command_history.join(', ')}`;
189
+ }
190
+ if (args.execution_results && args.execution_results.length > 0) {
191
+ enhancedContext += `\nExecution Results: ${args.execution_results.join('; ')}`;
192
+ }
193
+ const reevaluationResult = await this.performLLMCentricEvaluation(args.command, args.working_directory, [], // Empty history for function call context
194
+ enhancedContext);
195
+ // Convert result format
196
+ const simplifiedResult = {
197
+ evaluation_result: reevaluationResult.evaluation_result,
198
+ reasoning: reevaluationResult.reasoning,
199
+ requires_additional_context: {
200
+ command_history_depth: 0,
201
+ execution_results_count: 0,
202
+ user_intent_search_keywords: null,
203
+ user_intent_question: null
204
+ },
205
+ suggested_alternatives: ('suggested_alternatives' in reevaluationResult) ? reevaluationResult.suggested_alternatives || [] : []
206
+ };
207
+ logger.info('Function Call Additional Context Reevaluation', {
208
+ command: args.command,
209
+ context_length: enhancedContext.length,
210
+ result: simplifiedResult.evaluation_result
211
+ }, 'function-call');
212
+ return {
213
+ success: true,
214
+ result: simplifiedResult,
215
+ context: context
216
+ };
217
+ }
218
+ catch (error) {
219
+ return {
220
+ success: false,
221
+ error: `Additional context reevaluation failed: ${error instanceof Error ? error.message : String(error)}`,
222
+ context: context
223
+ };
224
+ }
225
+ }
226
+ /**
227
+ * Execute a Function Call by looking up the handler and calling it
228
+ */
229
+ async executeFunctionCall(functionName, args, context) {
230
+ const handler = this.functionCallHandlers[functionName];
231
+ if (!handler) {
232
+ return {
233
+ success: false,
234
+ error: `No handler found for function: ${functionName}`
235
+ };
236
+ }
237
+ try {
238
+ // Type-safe handler invocation with explicit casting
239
+ return await handler(args, context);
240
+ }
241
+ catch (error) {
242
+ return {
243
+ success: false,
244
+ error: `Handler execution failed: ${error instanceof Error ? error.message : String(error)}`
245
+ };
246
+ }
247
+ }
248
+ /**
249
+ * Public method for testing Function Call execution
250
+ * Execute a Function Call with OpenAI-style function call object
251
+ */
252
+ async executeTestFunctionCall(functionCall, context) {
253
+ try {
254
+ const args = JSON.parse(functionCall.arguments);
255
+ return await this.executeFunctionCall(functionCall.name, args, context);
256
+ }
257
+ catch (error) {
258
+ return {
259
+ success: false,
260
+ error: `Failed to parse function call arguments: ${error instanceof Error ? error.message : String(error)}`
261
+ };
262
+ }
263
+ }
264
+ /**
265
+ * Get the function call registry for testing
266
+ */
267
+ getFunctionCallRegistry() {
268
+ return new Map(Object.entries(this.functionCallHandlers));
269
+ }
270
+ setMCPServer(server) {
271
+ this.mcpServer = server;
272
+ }
273
+ /**
274
+ * Simple LLM-centric command safety evaluation
275
+ */
276
+ async evaluateCommandSafety(command, workingDirectory, history, comment, forceUserConfirm) {
277
+ const llmResult = await this.performLLMCentricEvaluation(command, workingDirectory, history, comment, forceUserConfirm);
278
+ // Direct conversion from LLMEvaluationResult to SafetyEvaluationResult
279
+ const elicitationResult = llmResult.elicitationResult;
280
+ return this.convertLLMResultToSafetyResult(llmResult, 'llm_required', elicitationResult);
281
+ }
282
+ /**
283
+ * Handle elicitation and add result to messages
284
+ */
285
+ async handleElicitationInLoop(command, question, messages) {
286
+ const { userIntent, elicitationResponse, elicitationResult } = await this.elicitUserIntent(command, question);
287
+ // Add detailed elicitation result to message chain with clear user decision
288
+ let elicitationResultMessage = `\n\nELICITATION RESULT:\nUser Action: ${elicitationResponse?.action || 'no_response'}\nTimestamp: ${getCurrentTimestamp()}`;
289
+ if (elicitationResponse?.action === 'accept') {
290
+ // Check command_execution_approved field for actual command execution decision
291
+ const commandApproved = elicitationResponse.content?.['command_execution_approved'] === true;
292
+ if (commandApproved) {
293
+ elicitationResultMessage += `\nUser Decision: APPROVED - User explicitly approved the command execution\nUser Intent: ${userIntent?.justification || 'Not provided'}`;
294
+ }
295
+ else {
296
+ elicitationResultMessage += `\nUser Decision: DECLINED - User engaged with elicitation but declined command execution\nReason: ${userIntent?.justification || 'The user has refused to allow this command to run'}`;
297
+ }
298
+ }
299
+ else if (elicitationResponse?.action === 'decline') {
300
+ elicitationResultMessage += `\nUser Decision: DECLINED - User explicitly declined the elicitation process\nReason: ${userIntent?.justification || 'The user has refused to allow this command to run'}`;
301
+ }
302
+ else if (elicitationResponse?.action === 'cancel') {
303
+ elicitationResultMessage += `\nUser Decision: CANCELLED - User cancelled the confirmation request\nReason: The user has cancelled the operation`;
304
+ }
305
+ else {
306
+ elicitationResultMessage += `\nUser Decision: NO_RESPONSE - No valid response received from user`;
307
+ }
308
+ messages.push({
309
+ role: 'user',
310
+ content: elicitationResultMessage,
311
+ timestamp: getCurrentTimestamp(),
312
+ type: 'elicitation'
313
+ });
314
+ return { userIntent, elicitationResponse, elicitationResult };
315
+ }
316
+ /**
317
+ * LLM-centric evaluation flow (improved with message-based approach)
318
+ */
319
+ async performLLMCentricEvaluation(command, workingDirectory, history, comment, forceUserConfirm) {
320
+ logger.debug('performLLMCentricEvaluation START', {
321
+ command,
322
+ workingDirectory,
323
+ forceUserConfirm
324
+ });
325
+ // Initialize system prompt and base user message before loop
326
+ const promptContext = {
327
+ command,
328
+ commandHistory: history.slice(0, 5).map((entry) => entry.command).filter((cmd) => cmd && cmd.trim().length > 0),
329
+ workingDirectory,
330
+ ...(comment && { comment }),
331
+ };
332
+ const { systemPrompt, userMessage: baseUserMessage } = await this.promptGenerator.generateSecurityEvaluationPrompt(promptContext);
333
+ // Initialize message chain - systemPrompt + baseUserMessage + chronological additions
334
+ const messages = [
335
+ { role: 'system', content: systemPrompt },
336
+ { role: 'user', content: baseUserMessage, timestamp: getCurrentTimestamp(), type: 'history' }
337
+ ];
338
+ let maxIteration = 5;
339
+ let hasElicitationBeenAttempted = false; // Track ELICITATION attempts
340
+ let capturedElicitationResult = undefined; // Store elicitation result
341
+ try {
342
+ while (true) {
343
+ logger.debug('LLM Evaluation iteration', {
344
+ remainingIterations: maxIteration,
345
+ messagesCount: messages.length,
346
+ hasElicitationBeenAttempted
347
+ });
348
+ if (maxIteration <= 0) {
349
+ return {
350
+ evaluation_result: 'deny',
351
+ reasoning: 'Maximum iterations reached - fallback to safe denial',
352
+ suggested_alternatives: [],
353
+ ...(capturedElicitationResult && { elicitationResult: capturedElicitationResult }),
354
+ };
355
+ }
356
+ maxIteration--;
357
+ let llmResult;
358
+ try {
359
+ llmResult = await this.callLLMForEvaluationWithMessages(messages, promptContext.command, forceUserConfirm);
360
+ }
361
+ catch (error) {
362
+ if (error instanceof ToolArgumentParseError) {
363
+ logger.warn('Tool argument parse error encountered - requesting corrected response', {
364
+ toolName: error.toolName,
365
+ reason: error.details.reason,
366
+ });
367
+ messages.push({
368
+ role: 'user',
369
+ content: error.buildFeedbackMessage(),
370
+ timestamp: getCurrentTimestamp(),
371
+ type: 'history',
372
+ });
373
+ continue;
374
+ }
375
+ throw error;
376
+ }
377
+ // ToolHandler pattern with early returns - clean architecture
378
+ switch (llmResult.evaluation_result) {
379
+ case 'allow':
380
+ case 'deny':
381
+ // Early return - no message chain manipulation needed for final decisions
382
+ return {
383
+ ...llmResult,
384
+ ...(capturedElicitationResult && { elicitationResult: capturedElicitationResult })
385
+ };
386
+ case 'user_confirm':
387
+ // CRITICAL: Check ELICITATION limit
388
+ if (hasElicitationBeenAttempted) {
389
+ logger.warn('user_confirm ELICITATION blocked - already attempted', {
390
+ command,
391
+ messagesCount: messages.length
392
+ });
393
+ return {
394
+ evaluation_result: 'deny',
395
+ reasoning: 'ELICITATION already attempted for user confirmation - defaulting to safe denial',
396
+ suggested_alternatives: [],
397
+ ...(capturedElicitationResult && { elicitationResult: capturedElicitationResult }),
398
+ };
399
+ }
400
+ // Add LLM's response to message chain before processing elicitation
401
+ messages.push({
402
+ role: 'assistant',
403
+ content: `Evaluation result: ${llmResult.evaluation_result}\nReasoning: ${llmResult.reasoning}`,
404
+ timestamp: getCurrentTimestamp()
405
+ });
406
+ hasElicitationBeenAttempted = true; // Mark ELICITATION as attempted
407
+ const userConfirmQuestion = llmResult.confirmation_question ||
408
+ "Do you want to proceed with this operation?";
409
+ const { userIntent: _userIntent, elicitationResponse: _elicitationResponse, elicitationResult } = await this.handleElicitationInLoop(command, userConfirmQuestion, messages);
410
+ // Capture elicitation result for final response
411
+ capturedElicitationResult = elicitationResult;
412
+ // Continue with LLM evaluation loop to get final decision based on user response
413
+ // Note: User response is already added to messages in handleElicitationInLoop
414
+ continue;
415
+ case 'ai_assistant_confirm':
416
+ // Early return for assistant confirmation - no loop continuation needed
417
+ return {
418
+ evaluation_result: 'ai_assistant_confirm',
419
+ reasoning: llmResult.assistant_request_message || llmResult.reasoning,
420
+ suggested_alternatives: llmResult.suggested_alternatives || [],
421
+ ...(llmResult.assistant_request_message && { assistant_request_message: llmResult.assistant_request_message }),
422
+ ...(llmResult.next_action && { next_action: llmResult.next_action }),
423
+ ...(capturedElicitationResult && { elicitationResult: capturedElicitationResult }),
424
+ };
425
+ case 'add_more_history':
426
+ // Add LLM's response to message chain before handling additional context
427
+ messages.push({
428
+ role: 'assistant',
429
+ content: `Evaluation result: ${llmResult.evaluation_result}\nReasoning: ${llmResult.reasoning}`,
430
+ timestamp: getCurrentTimestamp()
431
+ });
432
+ // Handle additional context requests by modifying messages
433
+ if (llmResult.command_history_depth || llmResult.execution_results_count || llmResult.user_intent_search_keywords) {
434
+ const additionalContext = {
435
+ command_history_depth: llmResult.command_history_depth || 0,
436
+ execution_results_count: llmResult.execution_results_count || 0,
437
+ user_intent_search_keywords: llmResult.user_intent_search_keywords || [],
438
+ user_intent_question: null
439
+ };
440
+ await this.handleAdditionalContextRequest(additionalContext, messages);
441
+ }
442
+ else {
443
+ // If no specific context is requested but we got add_more_history,
444
+ // add a note that we're proceeding with current information
445
+ messages.push({
446
+ role: 'user',
447
+ content: 'No additional context available. Please proceed with evaluation based on current information or provide a definitive decision.',
448
+ timestamp: getCurrentTimestamp(),
449
+ type: 'history'
450
+ });
451
+ }
452
+ continue; // Continue loop with additional context
453
+ default:
454
+ // TypeScript guarantees this case should never happen due to discriminated union
455
+ const exhaustiveCheck = llmResult;
456
+ logger.warn('Unexpected LLM evaluation result', {
457
+ result: exhaustiveCheck,
458
+ command
459
+ });
460
+ return {
461
+ evaluation_result: 'ai_assistant_confirm',
462
+ reasoning: `Unexpected LLM response - requesting AI assistant clarification`,
463
+ suggested_alternatives: [],
464
+ next_action: {
465
+ instruction: `Please clarify the response for command: ${command}`,
466
+ method: 'user_interaction',
467
+ expected_outcome: 'Clear guidance on how to proceed with the command',
468
+ executable_commands: []
469
+ }
470
+ };
471
+ }
472
+ }
473
+ }
474
+ catch (error) {
475
+ logger.error('LLM-centric evaluation failed', {
476
+ error: error instanceof Error ? error.message : String(error),
477
+ stack: error instanceof Error ? error.stack : undefined,
478
+ command,
479
+ workingDirectory
480
+ });
481
+ const errorMessage = error instanceof Error ? error.message : String(error);
482
+ // NO FALLBACK - throw error for proper handling upstream
483
+ throw new Error(`LLM evaluation failed: ${errorMessage}`);
484
+ }
485
+ }
486
+ /**
487
+ * Call LLM for evaluation using message-based approach
488
+ * Responsibility: Pure LLM communication and ToolCall parsing only
489
+ */
490
+ async callLLMForEvaluationWithMessages(messages, command, forceUserConfirm) {
491
+ try {
492
+ logger.debug('Pre-LLM Debug (Messages)', {
493
+ messagesCount: messages.length,
494
+ messagesPreview: messages.map(m => ({ role: m.role, type: m.type, contentLength: m.content.length }))
495
+ });
496
+ if (!this.chatAdapter) {
497
+ logger.error('CRITICAL ERROR: chatAdapter is not set - LLM evaluation cannot proceed');
498
+ throw new Error('chatAdapter is not set');
499
+ }
500
+ // Import the new individual security evaluation tools
501
+ const { newSecurityEvaluationTools } = await import('./security-tools.js');
502
+ logger.debug('Security tools imported successfully');
503
+ // Convert our message format to OpenAI format
504
+ const openAIMessages = messages.map(msg => ({
505
+ role: msg.role,
506
+ content: msg.content
507
+ }));
508
+ logger.debug('About to call LLM with Function Calling (Messages)', {
509
+ messagesCount: openAIMessages.length,
510
+ securityTools: JSON.stringify(newSecurityEvaluationTools, null, 2),
511
+ toolChoice: 'auto' // Let LLM choose which evaluation tool to use
512
+ });
513
+ // Use ChatCompletionAdapter with OpenAI API compatible format
514
+ const response = await this.chatAdapter.chatCompletion({
515
+ model: 'gpt-4-turbo', // Required by OpenAI API format
516
+ messages: openAIMessages,
517
+ max_tokens: 500,
518
+ temperature: 0.1,
519
+ tools: newSecurityEvaluationTools,
520
+ tool_choice: forceUserConfirm ? { type: 'function', function: { name: 'user_confirm' } } : 'auto'
521
+ });
522
+ logger.debug('LLM call completed successfully');
523
+ // Debug: Log the complete LLM response for analysis
524
+ const firstChoice = response.choices?.[0];
525
+ const message = firstChoice?.message;
526
+ logger.debug('=== COMPLETE LLM RESPONSE DEBUG (Messages) ===', {
527
+ responseType: typeof response,
528
+ responseKeys: Object.keys(response || {}),
529
+ hasToolCalls: !!message?.tool_calls,
530
+ toolCallsLength: message?.tool_calls?.length || 0,
531
+ fullContent: message?.content || '',
532
+ stopReason: firstChoice?.finish_reason,
533
+ fullResponse: JSON.stringify(response, null, 2)
534
+ });
535
+ // Process Function Call response - Parse tool calls into LLMEvaluationResult
536
+ if (message?.tool_calls && message.tool_calls.length > 0) {
537
+ const toolCall = message.tool_calls[0];
538
+ const toolName = toolCall?.function?.name;
539
+ // Ensure toolCall is not undefined
540
+ if (!toolCall) {
541
+ throw new Error('Tool call is undefined');
542
+ }
543
+ // Parse tool call to LLMEvaluationResult - simple data transformation only
544
+ return await this.parseToolCallToEvaluationResult(toolCall, toolName, command);
545
+ }
546
+ // Handle edge case: LLM returns tool_calls in content field as JSON string
547
+ if (message?.content && typeof message.content === 'string' && message.content.includes('tool_calls')) {
548
+ try {
549
+ const contentParsed = JSON.parse(message.content);
550
+ if (contentParsed.tool_calls && Array.isArray(contentParsed.tool_calls) && contentParsed.tool_calls.length > 0) {
551
+ const toolCall = contentParsed.tool_calls[0];
552
+ if (toolCall && toolCall.function && toolCall.function.name === 'evaluate_command_security') {
553
+ logger.warn('Found tool_calls in content field - parsing as Function Call');
554
+ const rawArgs = toolCall.function.arguments;
555
+ let result;
556
+ try {
557
+ result = JSON.parse(rawArgs);
558
+ }
559
+ catch (parseError) {
560
+ // Try JSON repair as fallback
561
+ logger.warn(`JSON parse failed for content tool_calls, attempting repair. Error: ${parseError}. Raw: ${rawArgs.substring(0, 200)}...`);
562
+ const repairResult = repairAndParseJson(rawArgs);
563
+ if (repairResult.success) {
564
+ result = repairResult.value;
565
+ logger.info(`JSON repair successful for content tool_calls after ${repairResult.repairAttempts?.length || 0} attempts`);
566
+ }
567
+ else {
568
+ const errorDetails = {
569
+ originalError: parseError instanceof Error ? parseError.message : String(parseError),
570
+ };
571
+ if (repairResult.repairAttempts && repairResult.repairAttempts.length > 0) {
572
+ errorDetails.repairAttempts = repairResult.repairAttempts;
573
+ }
574
+ if (repairResult.finalError) {
575
+ errorDetails.finalError = repairResult.finalError;
576
+ }
577
+ throw new ToolArgumentParseError(toolCall?.function?.name, rawArgs, 'JSON parsing failed for tool arguments', errorDetails);
578
+ }
579
+ }
580
+ if (!result || typeof result !== 'object') {
581
+ throw new ToolArgumentParseError(toolCall?.function?.name, rawArgs, 'Tool arguments must be a JSON object');
582
+ }
583
+ const typedResult = result;
584
+ // Validate required fields
585
+ const missingFields = [];
586
+ if (!typedResult['evaluation_result'])
587
+ missingFields.push('evaluation_result');
588
+ if (!typedResult['reasoning'])
589
+ missingFields.push('reasoning');
590
+ if (missingFields.length === 0) {
591
+ const evaluationResultRaw = typedResult['evaluation_result'];
592
+ const validResults = new Set([
593
+ 'allow',
594
+ 'deny',
595
+ 'add_more_history',
596
+ 'user_confirm',
597
+ 'ai_assistant_confirm',
598
+ ]);
599
+ if (typeof evaluationResultRaw !== 'string' || !validResults.has(evaluationResultRaw)) {
600
+ throw new ToolArgumentParseError(toolCall?.function?.name, rawArgs, `Invalid evaluation_result value: ${String(evaluationResultRaw)}`, {
601
+ receivedKeys: Object.keys(typedResult),
602
+ });
603
+ }
604
+ const reasoningValue = typedResult['reasoning'];
605
+ const expandedReasoning = this.expandCommandVariable(typeof reasoningValue === 'string' ? reasoningValue : String(reasoningValue ?? ''), command);
606
+ switch (evaluationResultRaw) {
607
+ case 'allow':
608
+ return {
609
+ evaluation_result: 'allow',
610
+ reasoning: expandedReasoning,
611
+ suggested_alternatives: Array.isArray(typedResult['suggested_alternatives'])
612
+ ? typedResult['suggested_alternatives']
613
+ : [],
614
+ };
615
+ case 'deny':
616
+ return {
617
+ evaluation_result: 'deny',
618
+ reasoning: expandedReasoning,
619
+ suggested_alternatives: Array.isArray(typedResult['suggested_alternatives'])
620
+ ? typedResult['suggested_alternatives']
621
+ : [],
622
+ };
623
+ case 'add_more_history': {
624
+ const historyDepth = typeof typedResult['command_history_depth'] === 'number'
625
+ ? typedResult['command_history_depth']
626
+ : 0;
627
+ const resultsCount = typeof typedResult['execution_results_count'] === 'number'
628
+ ? typedResult['execution_results_count']
629
+ : 0;
630
+ const keywords = Array.isArray(typedResult['user_intent_search_keywords'])
631
+ ? typedResult['user_intent_search_keywords'].filter((keyword) => typeof keyword === 'string')
632
+ : [];
633
+ return {
634
+ evaluation_result: 'add_more_history',
635
+ reasoning: expandedReasoning,
636
+ command_history_depth: historyDepth,
637
+ execution_results_count: resultsCount,
638
+ user_intent_search_keywords: keywords,
639
+ suggested_alternatives: [],
640
+ };
641
+ }
642
+ case 'user_confirm': {
643
+ const confirmationQuestion = typeof typedResult['confirmation_question'] === 'string'
644
+ ? typedResult['confirmation_question']
645
+ : 'Do you want to proceed?';
646
+ return {
647
+ evaluation_result: 'user_confirm',
648
+ reasoning: expandedReasoning,
649
+ confirmation_question: confirmationQuestion,
650
+ suggested_alternatives: [],
651
+ };
652
+ }
653
+ case 'ai_assistant_confirm': {
654
+ const nextActionRaw = typedResult['next_action'];
655
+ if (!nextActionRaw || typeof nextActionRaw !== 'object') {
656
+ throw new ToolArgumentParseError(toolCall?.function?.name, rawArgs, 'next_action is required for ai_assistant_confirm results', {
657
+ receivedKeys: Object.keys(typedResult),
658
+ });
659
+ }
660
+ const nextActionObj = nextActionRaw;
661
+ const instruction = typeof nextActionObj['instruction'] === 'string'
662
+ ? nextActionObj['instruction']
663
+ : 'Gather required information';
664
+ const method = typeof nextActionObj['method'] === 'string'
665
+ ? nextActionObj['method']
666
+ : 'Execute provided commands';
667
+ const expectedOutcome = typeof nextActionObj['expected_outcome'] === 'string'
668
+ ? nextActionObj['expected_outcome']
669
+ : 'Information for security evaluation';
670
+ const executableCommands = Array.isArray(nextActionObj['executable_commands'])
671
+ ? nextActionObj['executable_commands'].filter((cmd) => typeof cmd === 'string')
672
+ : undefined;
673
+ return {
674
+ evaluation_result: 'ai_assistant_confirm',
675
+ reasoning: expandedReasoning,
676
+ ...(typeof typedResult['assistant_request_message'] === 'string'
677
+ ? { assistant_request_message: typedResult['assistant_request_message'] }
678
+ : {}),
679
+ next_action: {
680
+ instruction,
681
+ method,
682
+ expected_outcome: expectedOutcome,
683
+ ...(executableCommands && executableCommands.length > 0
684
+ ? { executable_commands: executableCommands }
685
+ : {}),
686
+ },
687
+ suggested_alternatives: Array.isArray(typedResult['suggested_alternatives'])
688
+ ? typedResult['suggested_alternatives']
689
+ : [],
690
+ };
691
+ }
692
+ default:
693
+ throw new ToolArgumentParseError(toolCall?.function?.name, rawArgs, `Unsupported evaluation_result value: ${String(evaluationResultRaw)}`);
694
+ }
695
+ }
696
+ throw new ToolArgumentParseError(toolCall?.function?.name, rawArgs, `Missing required fields: ${missingFields.join(', ')}`, {
697
+ missingFields,
698
+ receivedKeys: Object.keys(typedResult),
699
+ });
700
+ }
701
+ }
702
+ }
703
+ catch (contentParseError) {
704
+ logger.warn(`Failed to parse content as JSON: ${contentParseError}`);
705
+ }
706
+ }
707
+ // If no tool calls, this is a critical failure - log detailed information
708
+ logger.error('CRITICAL: LLM did not return Function Call (Messages)', {
709
+ responseContent: message?.content || '',
710
+ responseStopReason: firstChoice?.finish_reason,
711
+ messagesUsed: JSON.stringify(openAIMessages, null, 2),
712
+ toolsProvided: JSON.stringify(newSecurityEvaluationTools, null, 2)
713
+ });
714
+ throw new Error('No valid tool call in response - Function Calling is required');
715
+ }
716
+ catch (error) {
717
+ if (error instanceof ToolArgumentParseError) {
718
+ logger.warn('Tool argument parse error propagated from LLM response', {
719
+ toolName: error.toolName,
720
+ reason: error.details.reason,
721
+ });
722
+ throw error;
723
+ }
724
+ // NO FALLBACK - Function Call must succeed
725
+ const errorMessage = error instanceof Error ? error.message : String(error);
726
+ logger.error('=== Exception Caught in LLM Evaluation (Messages) ===');
727
+ logger.error('Error type:', error?.constructor?.name || 'Unknown');
728
+ logger.error('Error message:', errorMessage);
729
+ if (error instanceof Error) {
730
+ logger.error('Error stack:', error.stack);
731
+ }
732
+ logger.error('Messages that caused error:', JSON.stringify(messages, null, 2));
733
+ logger.error('=== End Exception Debug ===');
734
+ throw new Error(`Function Call evaluation failed: ${errorMessage}`);
735
+ }
736
+ }
737
+ /**
738
+ * Parse ToolCall to LLMEvaluationResult - Simple data transformation only
739
+ * Responsibility: Convert LLM Function Call into standardized evaluation result
740
+ */
741
+ async parseToolCallToEvaluationResult(toolCall, toolName, command) {
742
+ // Parse based on tool name - unified logic for all tool types
743
+ switch (toolName) {
744
+ case 'allow':
745
+ return await this.parseAllowTool(toolCall, command);
746
+ case 'deny':
747
+ return await this.parseDenyTool(toolCall, command);
748
+ case 'user_confirm':
749
+ return await this.parseUserConfirmTool(toolCall, command);
750
+ case 'add_more_history':
751
+ return await this.parseAddMoreHistoryTool(toolCall, command);
752
+ case 'ai_assistant_confirm':
753
+ return await this.parseAiAssistantConfirmTool(toolCall, command);
754
+ default:
755
+ // If tool is not recognized, log and fallback
756
+ logger.warn('Unknown tool call received from LLM', {
757
+ toolName,
758
+ availableTools: ['allow', 'deny', 'user_confirm', 'add_more_history', 'ai_assistant_confirm'],
759
+ command
760
+ });
761
+ return {
762
+ evaluation_result: 'deny',
763
+ reasoning: `Unknown evaluation tool: ${toolName}. Defaulting to denial for security.`,
764
+ suggested_alternatives: []
765
+ };
766
+ }
767
+ }
768
+ /**
769
+ * Handle additional context requests by modifying messages
770
+ */
771
+ async handleAdditionalContextRequest(additionalContext, messages) {
772
+ // Handle request for more command history
773
+ if (additionalContext.command_history_depth && additionalContext.command_history_depth > 0) {
774
+ try {
775
+ const config = this.securityManager.getEnhancedConfig();
776
+ const moreHistory = await this.historyManager.searchHistory({
777
+ limit: additionalContext.command_history_depth || config.max_additional_history_for_context || 20,
778
+ });
779
+ if (moreHistory.length > 0) {
780
+ // Insert additional history right after system message
781
+ const historyContent = `ADDITIONAL COMMAND HISTORY:\n${moreHistory.map((entry, idx) => `${idx + 1}. ${entry.command} (${entry.timestamp})`).join('\n')}`;
782
+ messages.splice(1, 0, {
783
+ role: 'user',
784
+ content: historyContent,
785
+ timestamp: getCurrentTimestamp(),
786
+ type: 'history'
787
+ });
788
+ }
789
+ }
790
+ catch (error) {
791
+ console.error('Failed to get additional command history:', error);
792
+ }
793
+ }
794
+ // Handle request for execution results
795
+ if (additionalContext.execution_results_count && additionalContext.execution_results_count > 0) {
796
+ try {
797
+ // Find the last user message and append execution results
798
+ const lastUserMessageIndex = messages.map(m => m.role).lastIndexOf('user');
799
+ if (lastUserMessageIndex >= 0 && messages[lastUserMessageIndex]) {
800
+ const executionResults = await this.getRecentExecutionResults(additionalContext.execution_results_count);
801
+ if (executionResults.length > 0) {
802
+ messages[lastUserMessageIndex].content += `\n\nRECENT EXECUTION RESULTS:\n${executionResults.map((result, idx) => `${idx + 1}. Command: ${result.command}, Exit Code: ${result.exit_code}, Output: ${result.stdout?.substring(0, 200) || 'N/A'}`).join('\n')}`;
803
+ }
804
+ }
805
+ }
806
+ catch (error) {
807
+ console.error('Failed to get execution results:', error);
808
+ }
809
+ }
810
+ }
811
+ /**
812
+ * Get recent execution results for context
813
+ */
814
+ async getRecentExecutionResults(count) {
815
+ try {
816
+ const recentHistory = await this.historyManager.searchHistory({
817
+ limit: count,
818
+ });
819
+ // Transform history entries to execution results format
820
+ return recentHistory.map(entry => ({
821
+ command: entry.command,
822
+ exit_code: 0, // Default success - could be enhanced with actual exit codes
823
+ stdout: 'Execution completed', // Placeholder - could be enhanced with actual output
824
+ stderr: ''
825
+ }));
826
+ }
827
+ catch (error) {
828
+ console.error('Failed to get recent execution results:', error);
829
+ return [];
830
+ }
831
+ }
832
+ /**
833
+ * Elicit user intent using MCP protocol
834
+ */
835
+ async elicitUserIntent(command, userIntentQuestion) {
836
+ if (!this.securityManager.getEnhancedConfig().elicitation_enabled) {
837
+ console.warn('User intent elicitation is disabled');
838
+ return { userIntent: null, elicitationResponse: null };
839
+ }
840
+ const mcpServer = this.mcpServer;
841
+ if (!this.elicitationHandler && !mcpServer) {
842
+ throw new Error('No elicitation handler or MCP server available for elicitation');
843
+ }
844
+ // Use specific question from LLM if provided, otherwise use default message
845
+ // コマンド部分をshellコードブロックで囲む
846
+ const shellBlock = `\`\`\`shell\n${command}\n\`\`\``;
847
+ const elicitationMessage = userIntentQuestion
848
+ ? `🔐 SECURITY CONFIRMATION REQUIRED\n\nCommand:\n${shellBlock}\n\n${userIntentQuestion}`
849
+ : `🔐 SECURITY CONFIRMATION REQUIRED\n\nCommand:\n${shellBlock}\n\nThis command has been flagged for review. Please provide your intent:\n\n- What are you trying to accomplish?\n- Why is this specific command needed?\n- Are you sure this is what you want to execute?`;
850
+ const elicitationSchema = {
851
+ type: 'object',
852
+ properties: {
853
+ confirmed: {
854
+ type: 'boolean',
855
+ title: 'Execute this command?',
856
+ description: "Select 'Yes' if you understand the risks and want to proceed",
857
+ },
858
+ reason: {
859
+ type: 'string',
860
+ title: 'Why do you need to run this command?',
861
+ description: 'Briefly explain your intent',
862
+ },
863
+ },
864
+ required: ['confirmed'],
865
+ };
866
+ const startTime = Date.now();
867
+ const timestamp = getCurrentTimestamp();
868
+ try {
869
+ const elicitationRequest = {
870
+ message: elicitationMessage,
871
+ requestedSchema: elicitationSchema,
872
+ timeoutMs: 180000,
873
+ level: 'question',
874
+ };
875
+ let response;
876
+ if (this.elicitationHandler) {
877
+ response = await this.elicitationHandler(elicitationRequest);
878
+ }
879
+ else if (mcpServer) {
880
+ response = await mcpServer.request({ method: 'elicitation/create', params: elicitationRequest }, ElicitResultSchema);
881
+ }
882
+ else {
883
+ throw new Error('MCP server not available for elicitation');
884
+ }
885
+ const endTime = Date.now();
886
+ const duration = endTime - startTime;
887
+ if (response && typeof response === 'object' && 'action' in response) {
888
+ const result = response;
889
+ // Create base ElicitationResult
890
+ const elicitationResult = {
891
+ question_asked: elicitationMessage,
892
+ timestamp,
893
+ timeout_duration_ms: duration,
894
+ status: 'timeout', // Will be updated based on actual response
895
+ user_response: result.content,
896
+ };
897
+ if (result.action === 'accept' && result.content) {
898
+ const confirmed = result.content['confirmed'];
899
+ const reason = result.content['reason'] || 'No reason provided';
900
+ const userIntent = {
901
+ intent: `Execute command: ${command}`,
902
+ justification: reason,
903
+ timestamp: getCurrentTimestamp(),
904
+ confidence_level: confirmed ? 'high' : 'low',
905
+ elicitation_id: generateId(),
906
+ };
907
+ // Update ElicitationResult based on user's command execution decision
908
+ elicitationResult.status = confirmed ? 'confirmed' : 'declined';
909
+ elicitationResult.comment = confirmed
910
+ ? 'User confirmed command execution'
911
+ : 'User declined command execution';
912
+ // FIXED: Elicitation was accepted, but command execution decision is separate
913
+ // action should always be 'accept' since user engaged with elicitation
914
+ // The confirmed field indicates the actual command execution decision
915
+ return {
916
+ userIntent,
917
+ elicitationResponse: {
918
+ action: 'accept', // User accepted elicitation process
919
+ content: { ...result.content, command_execution_approved: confirmed } // Add clear execution decision
920
+ },
921
+ elicitationResult,
922
+ };
923
+ }
924
+ else {
925
+ // User declined or canceled the elicitation
926
+ elicitationResult.status = result.action === 'decline' ? 'declined' : 'canceled';
927
+ elicitationResult.comment = result.action === 'decline'
928
+ ? 'User declined elicitation process'
929
+ : 'User canceled elicitation process';
930
+ return {
931
+ userIntent: null,
932
+ elicitationResponse: { action: result.action },
933
+ elicitationResult,
934
+ };
935
+ }
936
+ }
937
+ throw new Error('Invalid elicitation response format');
938
+ }
939
+ catch (error) {
940
+ console.error('User intent elicitation failed:', error);
941
+ const endTime = Date.now();
942
+ const duration = endTime - startTime;
943
+ // Create ElicitationResult for timeout/error case
944
+ const elicitationResult = {
945
+ status: 'timeout',
946
+ question_asked: elicitationMessage,
947
+ timestamp,
948
+ timeout_duration_ms: duration,
949
+ comment: `Elicitation failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
950
+ };
951
+ return {
952
+ userIntent: null,
953
+ elicitationResponse: null,
954
+ elicitationResult,
955
+ };
956
+ }
957
+ }
958
+ /**
959
+ * Convert LLMEvaluationResult directly to SafetyEvaluationResult using factory pattern
960
+ */
961
+ convertLLMResultToSafetyResult(llmResult, _classification, elicitationResult) {
962
+ // Create result using factory based on evaluation result type
963
+ switch (llmResult.evaluation_result) {
964
+ case 'allow':
965
+ return SafetyEvaluationResultFactory.createAllow(llmResult.reasoning, {
966
+ llmEvaluationUsed: true,
967
+ suggestedAlternatives: llmResult.suggested_alternatives,
968
+ elicitationResult,
969
+ });
970
+ case 'deny':
971
+ return SafetyEvaluationResultFactory.createDeny(llmResult.reasoning, {
972
+ llmEvaluationUsed: true,
973
+ suggestedAlternatives: llmResult.suggested_alternatives,
974
+ elicitationResult,
975
+ });
976
+ case 'ai_assistant_confirm':
977
+ // next_actionが提供されていることを確認
978
+ if (!llmResult.next_action) {
979
+ throw new Error('next_action is required for ai_assistant_confirm results');
980
+ }
981
+ return SafetyEvaluationResultFactory.createAiAssistantConfirm(llmResult.reasoning, llmResult.next_action, {
982
+ llmEvaluationUsed: true,
983
+ suggestedAlternatives: llmResult.suggested_alternatives,
984
+ confirmationMessage: llmResult.assistant_request_message,
985
+ elicitationResult,
986
+ });
987
+ case 'user_confirm':
988
+ case 'add_more_history':
989
+ // これらは最終応答ではないため、内部処理でこれらが返される場合はエラー
990
+ throw new Error(`${llmResult.evaluation_result} results are not supported in final responses. These should be handled internally.`);
991
+ default:
992
+ // TypeScript guarantees this case should never happen due to discriminated union
993
+ const exhaustiveCheck = llmResult;
994
+ throw new Error(`Unexpected evaluation result in convertLLMResultToSafetyResult: ${JSON.stringify(exhaustiveCheck)}`);
995
+ }
996
+ }
997
+ /**
998
+ * Parse 'allow' tool - command is safe to execute
999
+ * Responsibility: Simple data transformation from ToolCall to LLMEvaluationResult
1000
+ */
1001
+ async parseAllowTool(toolCall, command) {
1002
+ try {
1003
+ const result = await this.parseToolArguments(toolCall, ['reasoning']);
1004
+ const reasoning = typeof result['reasoning'] === 'string' ? result['reasoning'] : 'Command allowed';
1005
+ const expandedReasoning = this.expandCommandVariable(reasoning, command);
1006
+ return {
1007
+ evaluation_result: 'allow',
1008
+ reasoning: expandedReasoning,
1009
+ suggested_alternatives: []
1010
+ };
1011
+ }
1012
+ catch (error) {
1013
+ logger.error('Failed to parse allow tool', { error, command });
1014
+ throw error;
1015
+ }
1016
+ }
1017
+ /**
1018
+ * Parse 'deny' tool - command is too dangerous
1019
+ * Responsibility: Simple data transformation from ToolCall to LLMEvaluationResult
1020
+ */
1021
+ async parseDenyTool(toolCall, command) {
1022
+ try {
1023
+ const result = await this.parseToolArguments(toolCall, ['reasoning', 'suggested_alternatives']);
1024
+ const reasoning = typeof result['reasoning'] === 'string' ? result['reasoning'] : 'Command denied';
1025
+ const expandedReasoning = this.expandCommandVariable(reasoning, command);
1026
+ const alternatives = Array.isArray(result['suggested_alternatives']) ? result['suggested_alternatives'] : [];
1027
+ return {
1028
+ evaluation_result: 'deny',
1029
+ reasoning: expandedReasoning,
1030
+ suggested_alternatives: alternatives
1031
+ };
1032
+ }
1033
+ catch (error) {
1034
+ logger.error('Failed to parse deny tool', { error, command });
1035
+ throw error;
1036
+ }
1037
+ }
1038
+ /**
1039
+ * Parse 'user_confirm' tool - requires user confirmation
1040
+ * Responsibility: Simple data transformation from ToolCall to LLMEvaluationResult
1041
+ */
1042
+ async parseUserConfirmTool(toolCall, command) {
1043
+ try {
1044
+ const result = await this.parseToolArguments(toolCall, ['reasoning', 'confirmation_question']);
1045
+ const reasoning = typeof result['reasoning'] === 'string' ? result['reasoning'] : 'Requires confirmation';
1046
+ const expandedReasoning = this.expandCommandVariable(reasoning, command);
1047
+ const question = typeof result['confirmation_question'] === 'string' ? result['confirmation_question'] : 'Do you want to proceed?';
1048
+ return {
1049
+ evaluation_result: 'user_confirm',
1050
+ reasoning: expandedReasoning,
1051
+ confirmation_question: question,
1052
+ suggested_alternatives: []
1053
+ };
1054
+ }
1055
+ catch (error) {
1056
+ logger.error('Failed to parse user_confirm tool', { error, command });
1057
+ throw error;
1058
+ }
1059
+ }
1060
+ /**
1061
+ * Parse 'add_more_history' tool - needs additional context
1062
+ * Responsibility: Simple data transformation from ToolCall to LLMEvaluationResult
1063
+ */
1064
+ async parseAddMoreHistoryTool(toolCall, command) {
1065
+ try {
1066
+ const result = await this.parseToolArguments(toolCall, ['reasoning', 'command_history_depth']);
1067
+ const reasoning = typeof result['reasoning'] === 'string' ? result['reasoning'] : 'Need more context';
1068
+ const expandedReasoning = this.expandCommandVariable(reasoning, command);
1069
+ const historyDepth = typeof result['command_history_depth'] === 'number' ? result['command_history_depth'] : 0;
1070
+ const resultsCount = typeof result['execution_results_count'] === 'number' ? result['execution_results_count'] : 0;
1071
+ const keywords = Array.isArray(result['user_intent_search_keywords']) ? result['user_intent_search_keywords'] : [];
1072
+ return {
1073
+ evaluation_result: 'add_more_history',
1074
+ reasoning: expandedReasoning,
1075
+ command_history_depth: historyDepth,
1076
+ execution_results_count: resultsCount,
1077
+ user_intent_search_keywords: keywords,
1078
+ suggested_alternatives: []
1079
+ };
1080
+ }
1081
+ catch (error) {
1082
+ logger.error('Failed to parse add_more_history tool', { error, command });
1083
+ throw error;
1084
+ }
1085
+ }
1086
+ /**
1087
+ * Parse 'ai_assistant_confirm' tool - needs AI assistant info
1088
+ * Responsibility: Simple data transformation from ToolCall to LLMEvaluationResult
1089
+ */
1090
+ async parseAiAssistantConfirmTool(toolCall, command) {
1091
+ try {
1092
+ const result = await this.parseToolArguments(toolCall, ['reasoning', 'assistant_request_message', 'next_action']);
1093
+ const reasoning = typeof result['reasoning'] === 'string' ? result['reasoning'] : 'AI assistant confirmation needed';
1094
+ const expandedReasoning = this.expandCommandVariable(reasoning, command);
1095
+ const message = typeof result['assistant_request_message'] === 'string' ? result['assistant_request_message'] : 'Please provide additional information';
1096
+ // Extract next_action - required for ai_assistant_confirm
1097
+ if (!result['next_action'] || typeof result['next_action'] !== 'object') {
1098
+ throw new Error('next_action is required for ai_assistant_confirm tool');
1099
+ }
1100
+ const nextActionObj = result['next_action'];
1101
+ const executableCommands = Array.isArray(nextActionObj['executable_commands']) ?
1102
+ nextActionObj['executable_commands'].filter((cmd) => typeof cmd === 'string') :
1103
+ undefined;
1104
+ const nextAction = {
1105
+ instruction: typeof nextActionObj['instruction'] === 'string' ? nextActionObj['instruction'] : 'Gather required information',
1106
+ method: typeof nextActionObj['method'] === 'string' ? nextActionObj['method'] : 'Execute provided commands',
1107
+ expected_outcome: typeof nextActionObj['expected_outcome'] === 'string' ? nextActionObj['expected_outcome'] : 'Information for security evaluation',
1108
+ ...(executableCommands && executableCommands.length > 0 && { executable_commands: executableCommands })
1109
+ };
1110
+ return {
1111
+ evaluation_result: 'ai_assistant_confirm',
1112
+ reasoning: expandedReasoning,
1113
+ assistant_request_message: message,
1114
+ suggested_alternatives: [],
1115
+ next_action: nextAction
1116
+ };
1117
+ }
1118
+ catch (error) {
1119
+ logger.error('Failed to parse ai_assistant_confirm tool', { error, command });
1120
+ throw error;
1121
+ }
1122
+ }
1123
+ /**
1124
+ * Helper: Parse and validate tool arguments with JSON repair fallback
1125
+ */
1126
+ async parseToolArguments(toolCall, requiredFields) {
1127
+ const rawArgs = toolCall.function.arguments;
1128
+ let parsedValue;
1129
+ try {
1130
+ parsedValue = JSON.parse(rawArgs);
1131
+ }
1132
+ catch (parseError) {
1133
+ logger.warn(`JSON parse failed, attempting repair. Error: ${parseError instanceof Error ? parseError.message : String(parseError)}. Raw: ${rawArgs.substring(0, 200)}...`);
1134
+ const repairResult = repairAndParseJson(rawArgs);
1135
+ if (repairResult.success) {
1136
+ parsedValue = repairResult.value;
1137
+ logger.info(`JSON repair successful after ${repairResult.repairAttempts?.length || 0} attempts`);
1138
+ }
1139
+ else {
1140
+ const errorDetails = {
1141
+ originalError: parseError instanceof Error ? parseError.message : String(parseError),
1142
+ };
1143
+ if (repairResult.repairAttempts && repairResult.repairAttempts.length > 0) {
1144
+ errorDetails.repairAttempts = repairResult.repairAttempts;
1145
+ }
1146
+ if (repairResult.finalError) {
1147
+ errorDetails.finalError = repairResult.finalError;
1148
+ }
1149
+ throw new ToolArgumentParseError(toolCall.function?.name, rawArgs, 'JSON parsing failed for tool arguments', errorDetails);
1150
+ }
1151
+ }
1152
+ if (!parsedValue || typeof parsedValue !== 'object' || Array.isArray(parsedValue)) {
1153
+ throw new ToolArgumentParseError(toolCall.function?.name, rawArgs, 'Tool arguments must be a JSON object', {
1154
+ receivedKeys: Array.isArray(parsedValue) ? parsedValue.map((_, idx) => idx.toString()) : [],
1155
+ });
1156
+ }
1157
+ const result = parsedValue;
1158
+ const missingFields = requiredFields.filter((field) => !(field in result));
1159
+ if (missingFields.length > 0) {
1160
+ throw new ToolArgumentParseError(toolCall.function?.name, rawArgs, `Missing required fields: ${missingFields.join(', ')}`, {
1161
+ missingFields,
1162
+ receivedKeys: Object.keys(result),
1163
+ });
1164
+ }
1165
+ return result;
1166
+ }
1167
+ /**
1168
+ * Expand $COMMAND variable in text with the actual command
1169
+ */
1170
+ expandCommandVariable(text, command) {
1171
+ if (!text || !command) {
1172
+ return text || '';
1173
+ }
1174
+ // Replace all instances of $COMMAND with the actual command
1175
+ // Use simple string replacement to avoid regex complications
1176
+ return text.replace(/\$COMMAND/g, command);
1177
+ }
1178
+ /**
1179
+ * Validator-side criteria adjustment
1180
+ * Allows internal adjustment of security evaluation criteria
1181
+ */
1182
+ async adjustValidatorCriteria(criteriaText, appendMode = false, backupExisting = true) {
1183
+ logger.info('Validator adjusting security criteria', {
1184
+ appendMode,
1185
+ backupExisting,
1186
+ criteriaLength: criteriaText.length
1187
+ });
1188
+ try {
1189
+ const result = await adjustCriteria(criteriaText, appendMode, backupExisting);
1190
+ logger.info('Validator criteria adjustment completed', {
1191
+ success: result.success,
1192
+ criteriaPath: result.criteriaPath,
1193
+ backupPath: result.backupPath
1194
+ });
1195
+ return result;
1196
+ }
1197
+ catch (error) {
1198
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
1199
+ logger.error('Validator criteria adjustment failed', { error: errorMessage });
1200
+ return {
1201
+ success: false,
1202
+ message: `Validator criteria adjustment failed: ${errorMessage}`,
1203
+ criteriaPath: ''
1204
+ };
1205
+ }
1206
+ }
1207
+ }
1208
+ //# sourceMappingURL=enhanced-evaluator.js.map