@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.
- package/LICENSE +21 -0
- package/README.md +114 -0
- package/dist/backoffice/index.d.ts +2 -0
- package/dist/backoffice/index.d.ts.map +1 -0
- package/dist/backoffice/index.js +47 -0
- package/dist/backoffice/index.js.map +1 -0
- package/dist/backoffice/server.d.ts +45 -0
- package/dist/backoffice/server.d.ts.map +1 -0
- package/dist/backoffice/server.js +610 -0
- package/dist/backoffice/server.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +525 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/config-manager.d.ts +80 -0
- package/dist/core/config-manager.d.ts.map +1 -0
- package/dist/core/config-manager.js +218 -0
- package/dist/core/config-manager.js.map +1 -0
- package/dist/core/enhanced-history-manager.d.ts +84 -0
- package/dist/core/enhanced-history-manager.d.ts.map +1 -0
- package/dist/core/enhanced-history-manager.js +319 -0
- package/dist/core/enhanced-history-manager.js.map +1 -0
- package/dist/core/file-manager.d.ts +79 -0
- package/dist/core/file-manager.d.ts.map +1 -0
- package/dist/core/file-manager.js +338 -0
- package/dist/core/file-manager.js.map +1 -0
- package/dist/core/file-storage-subscriber.d.ts +38 -0
- package/dist/core/file-storage-subscriber.d.ts.map +1 -0
- package/dist/core/file-storage-subscriber.js +132 -0
- package/dist/core/file-storage-subscriber.js.map +1 -0
- package/dist/core/monitoring-manager.d.ts +32 -0
- package/dist/core/monitoring-manager.d.ts.map +1 -0
- package/dist/core/monitoring-manager.js +296 -0
- package/dist/core/monitoring-manager.js.map +1 -0
- package/dist/core/process-manager.d.ts +105 -0
- package/dist/core/process-manager.d.ts.map +1 -0
- package/dist/core/process-manager.js +1374 -0
- package/dist/core/process-manager.js.map +1 -0
- package/dist/core/realtime-stream-subscriber.d.ts +93 -0
- package/dist/core/realtime-stream-subscriber.d.ts.map +1 -0
- package/dist/core/realtime-stream-subscriber.js +200 -0
- package/dist/core/realtime-stream-subscriber.js.map +1 -0
- package/dist/core/remote-http-client.d.ts +15 -0
- package/dist/core/remote-http-client.d.ts.map +1 -0
- package/dist/core/remote-http-client.js +60 -0
- package/dist/core/remote-http-client.js.map +1 -0
- package/dist/core/remote-process-service.d.ts +50 -0
- package/dist/core/remote-process-service.d.ts.map +1 -0
- package/dist/core/remote-process-service.js +20 -0
- package/dist/core/remote-process-service.js.map +1 -0
- package/dist/core/server-manager.d.ts +71 -0
- package/dist/core/server-manager.d.ts.map +1 -0
- package/dist/core/server-manager.js +680 -0
- package/dist/core/server-manager.js.map +1 -0
- package/dist/core/stream-publisher.d.ts +75 -0
- package/dist/core/stream-publisher.d.ts.map +1 -0
- package/dist/core/stream-publisher.js +127 -0
- package/dist/core/stream-publisher.js.map +1 -0
- package/dist/core/streaming-pipeline-reader.d.ts +67 -0
- package/dist/core/streaming-pipeline-reader.d.ts.map +1 -0
- package/dist/core/streaming-pipeline-reader.js +191 -0
- package/dist/core/streaming-pipeline-reader.js.map +1 -0
- package/dist/core/terminal-manager.d.ts +96 -0
- package/dist/core/terminal-manager.d.ts.map +1 -0
- package/dist/core/terminal-manager.js +515 -0
- package/dist/core/terminal-manager.js.map +1 -0
- package/dist/daemon/server.d.ts +8 -0
- package/dist/daemon/server.d.ts.map +1 -0
- package/dist/daemon/server.js +416 -0
- package/dist/daemon/server.js.map +1 -0
- package/dist/daemon/uds-transport.d.ts +31 -0
- package/dist/daemon/uds-transport.d.ts.map +1 -0
- package/dist/daemon/uds-transport.js +149 -0
- package/dist/daemon/uds-transport.js.map +1 -0
- package/dist/executor/server.d.ts +20 -0
- package/dist/executor/server.d.ts.map +1 -0
- package/dist/executor/server.js +375 -0
- package/dist/executor/server.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +73 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime/daemon-runtime.d.ts +4 -0
- package/dist/runtime/daemon-runtime.d.ts.map +1 -0
- package/dist/runtime/daemon-runtime.js +4 -0
- package/dist/runtime/daemon-runtime.js.map +1 -0
- package/dist/runtime/index.d.ts +3 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +3 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/tool-runtime.d.ts +52 -0
- package/dist/runtime/tool-runtime.d.ts.map +1 -0
- package/dist/runtime/tool-runtime.js +161 -0
- package/dist/runtime/tool-runtime.js.map +1 -0
- package/dist/security/chat-completion-adapter.d.ts +443 -0
- package/dist/security/chat-completion-adapter.d.ts.map +1 -0
- package/dist/security/chat-completion-adapter.js +475 -0
- package/dist/security/chat-completion-adapter.js.map +1 -0
- package/dist/security/enhanced-evaluator.d.ts +139 -0
- package/dist/security/enhanced-evaluator.d.ts.map +1 -0
- package/dist/security/enhanced-evaluator.js +1208 -0
- package/dist/security/enhanced-evaluator.js.map +1 -0
- package/dist/security/evaluator-types.d.ts +614 -0
- package/dist/security/evaluator-types.d.ts.map +1 -0
- package/dist/security/evaluator-types.js +124 -0
- package/dist/security/evaluator-types.js.map +1 -0
- package/dist/security/manager.d.ts +76 -0
- package/dist/security/manager.d.ts.map +1 -0
- package/dist/security/manager.js +445 -0
- package/dist/security/manager.js.map +1 -0
- package/dist/security/security-llm-prompt-generator.d.ts +105 -0
- package/dist/security/security-llm-prompt-generator.d.ts.map +1 -0
- package/dist/security/security-llm-prompt-generator.js +323 -0
- package/dist/security/security-llm-prompt-generator.js.map +1 -0
- package/dist/security/security-tools.d.ts +174 -0
- package/dist/security/security-tools.d.ts.map +1 -0
- package/dist/security/security-tools.js +159 -0
- package/dist/security/security-tools.js.map +1 -0
- package/dist/security/validator-criteria-manager.d.ts +47 -0
- package/dist/security/validator-criteria-manager.d.ts.map +1 -0
- package/dist/security/validator-criteria-manager.js +169 -0
- package/dist/security/validator-criteria-manager.js.map +1 -0
- package/dist/tools/shell-tools.d.ts +474 -0
- package/dist/tools/shell-tools.d.ts.map +1 -0
- package/dist/tools/shell-tools.js +861 -0
- package/dist/tools/shell-tools.js.map +1 -0
- package/dist/types/enhanced-security.d.ts +529 -0
- package/dist/types/enhanced-security.d.ts.map +1 -0
- package/dist/types/enhanced-security.js +286 -0
- package/dist/types/enhanced-security.js.map +1 -0
- package/dist/types/index.d.ts +282 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +158 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/quick-schemas.d.ts +177 -0
- package/dist/types/quick-schemas.d.ts.map +1 -0
- package/dist/types/quick-schemas.js +113 -0
- package/dist/types/quick-schemas.js.map +1 -0
- package/dist/types/response-schemas.d.ts +41 -0
- package/dist/types/response-schemas.d.ts.map +1 -0
- package/dist/types/response-schemas.js +41 -0
- package/dist/types/response-schemas.js.map +1 -0
- package/dist/types/schemas.d.ts +578 -0
- package/dist/types/schemas.d.ts.map +1 -0
- package/dist/types/schemas.js +498 -0
- package/dist/types/schemas.js.map +1 -0
- package/dist/utils/criteria-manager.d.ts +47 -0
- package/dist/utils/criteria-manager.d.ts.map +1 -0
- package/dist/utils/criteria-manager.js +228 -0
- package/dist/utils/criteria-manager.js.map +1 -0
- package/dist/utils/errors.d.ts +27 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +67 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/helpers.d.ts +85 -0
- package/dist/utils/helpers.d.ts.map +1 -0
- package/dist/utils/helpers.js +400 -0
- package/dist/utils/helpers.js.map +1 -0
- package/dist/utils/json-repair.d.ts +23 -0
- package/dist/utils/json-repair.d.ts.map +1 -0
- package/dist/utils/json-repair.js +208 -0
- package/dist/utils/json-repair.js.map +1 -0
- package/dist/utils/process-utils.d.ts +31 -0
- package/dist/utils/process-utils.d.ts.map +1 -0
- package/dist/utils/process-utils.js +217 -0
- package/dist/utils/process-utils.js.map +1 -0
- package/dist/utils/server-helpers.d.ts +4 -0
- package/dist/utils/server-helpers.d.ts.map +1 -0
- package/dist/utils/server-helpers.js +10 -0
- package/dist/utils/server-helpers.js.map +1 -0
- package/dist/utils/sse.d.ts +2 -0
- package/dist/utils/sse.d.ts.map +1 -0
- package/dist/utils/sse.js +6 -0
- package/dist/utils/sse.js.map +1 -0
- 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
|