aiexecode 1.0.84 → 1.0.89

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.

Potentially problematic release.


This version of aiexecode might be problematic. Click here for more details.

@@ -803,7 +803,7 @@ export function App({ onSubmit, onClearScreen, onExit, commands = [], model, ver
803
803
  nextItem
804
804
  });
805
805
  })
806
- , []);
806
+ , []);
807
807
 
808
808
  const renderHistoryItem = useCallback((item, index, prefix = 'history', allItems = []) => {
809
809
  const nextItem = allItems[index + 1];
@@ -1027,16 +1027,66 @@ export function App({ onSubmit, onClearScreen, onExit, commands = [], model, ver
1027
1027
  );
1028
1028
  }
1029
1029
 
1030
- return React.createElement(Box, { flexDirection: "column", padding: 1 },
1030
+ return React.createElement(Box, { flexDirection: "column", padding: 0 },
1031
1031
  // History area (grows to fill available space, shrinks when needed)
1032
1032
  React.createElement(Box, {
1033
1033
  flexDirection: "column",
1034
1034
  flexGrow: 1,
1035
1035
  flexShrink: 1,
1036
- overflow: 'hidden' // Prevent history from pushing out fixed elements
1036
+ overflow: 'hidden', // Prevent history from pushing out fixed elements
1037
+ gap: 0 // No gap between children
1037
1038
  },
1039
+ // React.createElement(BlankLine, {
1040
+ // reason: 'separator between static and dynamic',
1041
+ // afterType: 'static_area',
1042
+ // beforeType: 'dynamic_area',
1043
+ // content: '---DYNAMIC1---'
1044
+ // }),
1045
+
1046
+ // ===== Static 영역 시작 =====
1038
1047
  // Static items: Header와 히스토리 포함
1039
- React.createElement(Static, { items: staticItems }, (item) => item)
1048
+ React.createElement(Box, { marginBottom: 0, paddingBottom: 0 },
1049
+ React.createElement(Static, { items: staticItems }, (item) => item)
1050
+ ),
1051
+ // ===== Static 영역 끝 =====
1052
+
1053
+ // Static과 Dynamic 사이 구분선
1054
+ // React.createElement(BlankLine, {
1055
+ // reason: 'separator between static and dynamic',
1056
+ // afterType: 'static_area',
1057
+ // beforeType: 'dynamic_area',
1058
+ // content: '---DYNAMIC2---'
1059
+ // }),
1060
+
1061
+ // ===== Dynamic 영역 시작 =====
1062
+ // Dynamic pending items: 실행 중인 도구들을 즉시 표시
1063
+ pendingHistory.length > 0 && pendingHistory.flatMap((item, index) => {
1064
+ const nextItem = pendingHistory[index + 1];
1065
+ const elements = [
1066
+ React.createElement(ConversationItem, {
1067
+ key: `pending-${index}`,
1068
+ item,
1069
+ terminalWidth,
1070
+ nextItem,
1071
+ isPending: true // Pending 상태 전달
1072
+ })
1073
+ ];
1074
+
1075
+ // 각 pending 아이템 뒤에 BlankLine 추가
1076
+ elements.push(
1077
+ React.createElement(BlankLine, {
1078
+ key: `pending-blank-${index}`,
1079
+ reason: 'after pending item',
1080
+ afterType: item.type,
1081
+ afterToolName: item.toolName,
1082
+ beforeType: nextItem ? nextItem.type : null,
1083
+ content: ' '
1084
+ })
1085
+ );
1086
+
1087
+ return elements;
1088
+ })
1089
+ // ===== Dynamic 영역 끝 =====
1040
1090
  ),
1041
1091
 
1042
1092
  // Fixed input/control area (does not shrink, stays at bottom)
@@ -47,14 +47,16 @@ import { createDebugLogger } from '../../util/debug_log.js';
47
47
 
48
48
  const debugLog = createDebugLogger('ui_components.log', 'BlankLine');
49
49
 
50
- export function BlankLine({ reason, afterType, afterToolName, beforeType }) {
50
+ export function BlankLine({ reason, afterType, afterToolName, beforeType, content = ' ' }) {
51
+ // if (content === ' ') content = 'aaaaa';
51
52
  // 디버그 모드에서만 빈 줄 정보 로깅
52
53
  React.useEffect(() => {
53
- debugLog(`[BlankLine] Rendered - reason: ${reason}, after: ${afterType}${afterToolName ? `(${afterToolName})` : ''}, before: ${beforeType || 'N/A'}`);
54
+ debugLog(`[BlankLine] Rendered - reason: ${reason}, after: ${afterType}${afterToolName ? `(${afterToolName})` : ''}, before: ${beforeType || 'N/A'}, content: ${content}`);
54
55
  }, []);
55
56
 
56
57
  // 빈 줄은 공백 문자를 포함한 Text로 렌더링
57
58
  // Ink의 flexDirection: 'column'에서 각 컴포넌트는 한 줄씩 차지하므로
58
59
  // Text(' ')는 한 줄의 빈 줄로 렌더링됨
59
- return React.createElement(Text, null, ' ');
60
+ // content prop으로 표시할 내용을 커스터마이징 가능
61
+ return React.createElement(Text, null, content);
60
62
  }
@@ -79,11 +79,11 @@ const TYPE_CONFIG = {
79
79
  assistant_progress: { icon: ' ', color: theme.text.secondary, bold: false }, // 중간 안내 메시지
80
80
  system: { icon: '* ', color: theme.text.secondary, bold: false },
81
81
  error: { icon: '✗ ', color: theme.status.error, bold: true },
82
- tool: { icon: ' ', color: theme.status.success, bold: false },
83
- tool_start: { icon: ' ', color: theme.status.success, bold: true },
82
+ tool: { icon: ' ', color: theme.status.success, bold: false },
83
+ tool_start: { icon: ' ', color: theme.status.success, bold: true },
84
84
  tool_result: { icon: ' ㄴ', color: theme.text.secondary, bold: false },
85
85
  thinking: { icon: '◇ ', color: theme.text.link, bold: false },
86
- code_execution: { icon: ' ', color: theme.status.warning, bold: false },
86
+ code_execution: { icon: ' ', color: theme.status.warning, bold: false },
87
87
  code_result: { icon: '◀ ', color: theme.status.success, bold: false },
88
88
  default: { icon: '• ', color: theme.text.primary, bold: false }
89
89
  };
@@ -92,7 +92,7 @@ function getTypeConfig(type) {
92
92
  return TYPE_CONFIG[type] || TYPE_CONFIG.default;
93
93
  }
94
94
 
95
- function CodeExecutionDisplay({ item, hasFollowingResult, nextItem }) {
95
+ function CodeExecutionDisplay({ item, hasFollowingResult, nextItem, isPending }) {
96
96
  const config = getTypeConfig(item.type);
97
97
 
98
98
  const languageName = item.language.charAt(0).toUpperCase() + item.language.slice(1);
@@ -101,6 +101,19 @@ function CodeExecutionDisplay({ item, hasFollowingResult, nextItem }) {
101
101
  const resultStderr = item.result?.stderr || nextItem?.stderr;
102
102
  const isDenied = resultStderr?.includes('User denied code execution');
103
103
 
104
+ // Blinking icon for pending state
105
+ const [showIcon, setShowIcon] = useState(true);
106
+ useEffect(() => {
107
+ if (isPending) {
108
+ const interval = setInterval(() => {
109
+ setShowIcon(prev => !prev);
110
+ }, 500); // 0.5초 간격으로 깜빡임
111
+ return () => clearInterval(interval);
112
+ } else {
113
+ setShowIcon(true);
114
+ }
115
+ }, [isPending]);
116
+
104
117
  /**
105
118
  * 간격: marginBottom 항상 0
106
119
  * 빈 줄은 BlankLine 컴포넌트로 관리됨
@@ -109,10 +122,13 @@ function CodeExecutionDisplay({ item, hasFollowingResult, nextItem }) {
109
122
  */
110
123
  const marginBottom = 0;
111
124
 
125
+ // Pending 상태일 때는 회색, 아니면 기본 색상
126
+ const iconColor = isPending ? theme.text.secondary : config.color;
127
+
112
128
  if (isDenied) {
113
129
  return React.createElement(Box, { flexDirection: "column", marginBottom, marginLeft: 2 },
114
130
  React.createElement(Box, { flexDirection: "row", marginBottom: 0 },
115
- React.createElement(Text, { color: config.color, bold: true }, config.icon),
131
+ React.createElement(Text, { color: iconColor, bold: true }, showIcon ? config.icon : ' '),
116
132
  React.createElement(Text, { color: theme.text.primary }, 'Executing '),
117
133
  React.createElement(Text, { color: theme.status.warning, bold: true }, languageName)
118
134
  ),
@@ -124,7 +140,7 @@ function CodeExecutionDisplay({ item, hasFollowingResult, nextItem }) {
124
140
 
125
141
  return React.createElement(Box, { flexDirection: "column", marginBottom, marginLeft: 2 },
126
142
  React.createElement(Box, { flexDirection: "row", marginBottom: 0 },
127
- React.createElement(Text, { color: config.color, bold: true }, config.icon),
143
+ React.createElement(Text, { color: iconColor, bold: true }, showIcon ? config.icon : ' '),
128
144
  React.createElement(Text, { color: theme.text.primary }, 'Executing '),
129
145
  React.createElement(Text, { color: theme.status.warning, bold: true }, languageName),
130
146
  // React.createElement(Text, { color: theme.text.primary }, ' code:')
@@ -177,6 +193,19 @@ function StandardDisplay({ item, isPending, hasFollowingResult, nextItem, isLast
177
193
  const { type, text, operations = [], toolName, toolInput, args } = item;
178
194
  const config = getTypeConfig(type);
179
195
 
196
+ // Blinking icon for pending tool_start
197
+ const [showIcon, setShowIcon] = useState(true);
198
+ useEffect(() => {
199
+ if (isPending && type === 'tool_start') {
200
+ const interval = setInterval(() => {
201
+ setShowIcon(prev => !prev);
202
+ }, 500); // 0.5초 간격으로 깜빡임
203
+ return () => clearInterval(interval);
204
+ } else {
205
+ setShowIcon(true);
206
+ }
207
+ }, [isPending, type]);
208
+
180
209
  debugLog('---------- StandardDisplay START ----------');
181
210
  debugLog(`type: ${type}, toolName: ${toolName || 'N/A'}`);
182
211
  debugLog(`text: ${typeof text === 'string' ? text?.substring(0, 100) : JSON.stringify(text)?.substring(0, 100) || 'N/A'}...`);
@@ -727,13 +756,17 @@ function StandardDisplay({ item, isPending, hasFollowingResult, nextItem, isLast
727
756
 
728
757
  // Check if this tool was denied by user
729
758
  const originalResult = item.result?.originalResult || nextItem?.result?.originalResult;
730
- const isDenied = originalResult?.operation_successful === false &&
759
+ const isDenied = originalResult?.operation_successful === false &&
731
760
  originalResult?.error_message === 'User denied tool execution';
732
761
 
762
+ // Pending 상태일 때는 회색, 아니면 기본 색상
763
+ const iconColor = isPending ? theme.text.secondary : config.color;
764
+ const displayIcon = showIcon ? config.icon : ' ';
765
+
733
766
  if (isDenied) {
734
767
  return React.createElement(Box, { flexDirection: "column", marginBottom, marginTop, marginLeft: 2 },
735
768
  React.createElement(Box, { flexDirection: "row" },
736
- React.createElement(Text, { color: config.color, bold: config.bold }, config.icon),
769
+ React.createElement(Text, { color: iconColor, bold: config.bold }, displayIcon),
737
770
  React.createElement(Text, { color: 'white', bold: true }, displayName)
738
771
  ),
739
772
  React.createElement(Box, { marginLeft: 2, marginTop: 0 },
@@ -744,7 +777,7 @@ function StandardDisplay({ item, isPending, hasFollowingResult, nextItem, isLast
744
777
 
745
778
  return React.createElement(Box, { flexDirection: "column", marginBottom, marginTop, marginLeft: 2 },
746
779
  React.createElement(Box, { flexDirection: "row" },
747
- React.createElement(Text, { color: config.color, bold: config.bold }, config.icon),
780
+ React.createElement(Text, { color: iconColor, bold: config.bold }, displayIcon),
748
781
  React.createElement(Text, {
749
782
  color: 'white',
750
783
  bold: true
@@ -824,7 +857,7 @@ export function ConversationItem({ item, isPending = false, terminalWidth, nextI
824
857
  if (item.type === 'code_execution' && item.code && item.language) {
825
858
  const hasFollowingResult = nextItem && nextItem.type === 'code_result';
826
859
  debugLog('Rendering CodeExecutionDisplay');
827
- return React.createElement(CodeExecutionDisplay, { item, hasFollowingResult, nextItem });
860
+ return React.createElement(CodeExecutionDisplay, { item, hasFollowingResult, nextItem, isPending });
828
861
  }
829
862
 
830
863
  if (item.type === 'code_result') {
@@ -225,6 +225,9 @@ export async function execPythonCode(python_code, args = [], timeoutMs = 1200000
225
225
  debugLog(`[execPythonCode] Args: ${JSON.stringify(args)}`);
226
226
  debugLog(`[execPythonCode] Timeout: ${timeoutMs}ms`);
227
227
 
228
+ // Intentional delay for testing pending state
229
+ await new Promise(resolve => setTimeout(resolve, 13));
230
+
228
231
  debugLog(`[execPythonCode] Creating/checking virtual environment...`);
229
232
  const venvPath = await makePythonVirtualEnv(process.env.PYTHON_VENV_PATH || "venv");
230
233
 
@@ -329,6 +332,9 @@ export async function execShellScript(script, timeoutMs = 1200000) {
329
332
  debugLog(`[execShellScript] Script preview (first 200 chars): ${script.substring(0, 200)}${script.length > 200 ? '...' : ''}`);
330
333
  debugLog(`[execShellScript] Timeout: ${timeoutMs}ms`);
331
334
 
335
+ // Intentional delay for testing pending state
336
+ await new Promise(resolve => setTimeout(resolve, 13));
337
+
332
338
  debugLog(`[execShellScript] Finding shell executable...`);
333
339
  let shellPath = await whichCommand("bash") || await whichCommand("sh");
334
340
  if (!shellPath) {
@@ -15,8 +15,7 @@ const debugLog = createDebugLogger('mcp_integration.log', 'mcp_integration');
15
15
  */
16
16
 
17
17
  export class MCPIntegration {
18
- constructor(projectRoot = process.cwd()) {
19
- this.projectRoot = projectRoot;
18
+ constructor() {
20
19
  this.mcpAgent = null;
21
20
  this.isInitialized = false;
22
21
  this.mcpTools = new Map(); // toolName -> { server, schema, execute }
@@ -27,61 +26,84 @@ export class MCPIntegration {
27
26
  */
28
27
  async initialize() {
29
28
  try {
30
- debugLog('MCP Integration initializing...');
29
+ debugLog('[MCP] Integration initializing...');
31
30
 
32
- // 전역 설정 로드
31
+ // MCP 서버 설정 파일(~/.aiexe/mcp_config.json)에서 전역 설정 로드
32
+ debugLog('[MCP] Loading MCP config from ~/.aiexe/mcp_config.json');
33
33
  const mergedServers = await loadMergedMcpConfig();
34
34
 
35
- // Skip if no MCP servers are configured
35
+ // MCP 서버가 설정되지 않은 경우 초기화 중단
36
36
  if (Object.keys(mergedServers).length === 0) {
37
- debugLog('No MCP servers configured.');
38
- debugLog(' Use "aiexecode mcp add" to configure servers.');
37
+ debugLog('[MCP] No MCP servers configured.');
38
+ debugLog('[MCP] Use "aiexecode mcp add" to configure servers.');
39
39
  return { success: false, reason: 'no_servers' };
40
40
  }
41
41
 
42
- // 환경 변수 확장
42
+ debugLog(`[MCP] Found ${Object.keys(mergedServers).length} server(s) in config: ${Object.keys(mergedServers).join(', ')}`);
43
+
44
+ // 각 서버 설정의 환경 변수 확장 (${VAR} 형식을 실제 값으로 치환)
45
+ debugLog('[MCP] Expanding environment variables in server configs...');
43
46
  const servers = {};
44
47
  for (const [name, config] of Object.entries(mergedServers)) {
45
48
  try {
49
+ debugLog(`[MCP] Processing server '${name}' (type: ${config.type})`);
46
50
  servers[name] = expandEnvVars(config);
51
+ debugLog(`[MCP] ✓ Server '${name}' config ready`);
47
52
  } catch (error) {
48
- debugLog(`Error expanding env vars for server '${name}': ${error.message}`);
49
- debugLog(` Skipping server '${name}'`);
53
+ debugLog(`[MCP] ✗ Error expanding env vars for server '${name}': ${error.message}`);
54
+ debugLog(`[MCP] Skipping server '${name}'`);
50
55
  continue;
51
56
  }
52
57
  }
53
58
 
54
59
  if (Object.keys(servers).length === 0) {
55
- debugLog('No valid MCP servers after configuration processing.');
60
+ debugLog('[MCP] No valid MCP servers after configuration processing.');
56
61
  return { success: false, reason: 'no_valid_servers' };
57
62
  }
58
63
 
59
- // MCP 설정 객체 생성
64
+ debugLog(`[MCP] ${Object.keys(servers).length} server(s) ready for connection`);
65
+
66
+ // mcp-agent-lib에 전달할 설정 객체 생성
60
67
  const config = {
61
68
  mcpServers: servers
62
69
  };
63
70
 
64
- // mcp-agent-lib를 사용하여 MCP Agent 생성
71
+ // mcp-agent-lib의 createMCPAgent()호출하여 MCP Agent 인스턴스 생성
72
+ // MCP Agent는 여러 MCP 서버와의 연결 및 통신을 관리하는 핵심 객체
73
+ debugLog('[MCP] Creating MCP Agent instance via mcp-agent-lib...');
74
+ const logLevel = process.env.MCP_LOG_LEVEL || 'info';
75
+ const consoleDebug = process.env.MCP_DEBUG === 'true';
76
+ debugLog(`[MCP] Log level: ${logLevel}, Console debug: ${consoleDebug}`);
77
+
65
78
  this.mcpAgent = await createMCPAgent({
66
- logLevel: process.env.MCP_LOG_LEVEL || 'info',
67
- enableConsoleDebug: process.env.MCP_DEBUG === 'true'
79
+ logLevel: logLevel,
80
+ enableConsoleDebug: consoleDebug
68
81
  });
82
+ debugLog('[MCP] MCP Agent instance created');
69
83
 
70
- // MCP Agent 초기화 (서버 연결)
84
+ // MCP Agent 초기화: 설정된 모든 MCP 서버에 연결 시도
85
+ // stdio, http, sse 등 다양한 전송 방식을 지원
86
+ debugLog('[MCP] Initializing MCP Agent and connecting to servers...');
71
87
  await this.mcpAgent.initialize(config);
88
+ debugLog('[MCP] MCP Agent initialization complete');
72
89
 
73
- // 사용 가능한 도구 목록 가져오기
90
+ // 연결된 모든 MCP 서버로부터 사용 가능한 도구(tool) 목록 조회
91
+ debugLog('[MCP] Fetching available tools from connected servers...');
74
92
  const availableTools = this.mcpAgent.getAvailableTools();
75
93
 
76
- debugLog(`MCP Agent initialization complete: ${availableTools.length} tool(s) available`);
94
+ debugLog(`[MCP] Found ${availableTools.length} tool(s) across all servers`);
77
95
 
78
- // 도구 정보를 맵에 저장
96
+ // 도구를 내부 맵에 저장하고 AI Agent 시스템에 등록
97
+ debugLog('[MCP] Registering tools to AI Agent system...');
79
98
  for (const tool of availableTools) {
80
- // Description에 서버 정보를 명확하게 표시 (AI가 서버를 식별할 수 있도록)
99
+ debugLog(`[MCP] Registering tool '${tool.name}' from server '${tool.server}'`);
100
+
101
+ // AI가 도구의 출처 서버를 명확히 인식할 수 있도록 설명에 서버 이름 추가
81
102
  const enhancedDescription = tool.description
82
103
  ? `[${tool.server} MCP] - ${tool.description}`
83
104
  : `MCP tool from ${tool.server}`;
84
105
 
106
+ // 도구 정보를 mcpTools Map에 저장 (toolName -> { server, schema, execute })
85
107
  this.mcpTools.set(tool.name, {
86
108
  server: tool.server,
87
109
  schema: {
@@ -93,46 +115,66 @@ export class MCPIntegration {
93
115
  required: []
94
116
  }
95
117
  },
118
+ // 도구 실행 함수: MCP Agent를 통해 실제 MCP 서버에 도구 실행 요청 전송
96
119
  execute: async (args) => {
97
- return await this.mcpAgent.executeTool(tool.name, args);
120
+ debugLog(`[MCP] Executing tool '${tool.name}' on server '${tool.server}'`);
121
+ debugLog(`[MCP] Args: ${JSON.stringify(args).substring(0, 200)}`);
122
+ const result = await this.mcpAgent.executeTool(tool.name, args);
123
+ debugLog(`[MCP] Result: success=${result.success}`);
124
+ return result;
98
125
  }
99
126
  });
100
127
 
101
- // UI 표시 설정 등록
128
+ // UI 시스템에 MCP 도구 표시 정보 등록
102
129
  registerMCPToolDisplay(tool.name, tool.server);
103
130
 
104
- // 승인 시스템에 MCP 도구 등록
131
+ // 도구 승인 시스템에 MCP 도구 등록 (사용자 승인 필요 시 처리)
105
132
  registerMCPTool(tool.name, tool.server, tool.description || '');
106
133
 
107
- debugLog(` - ${tool.name} (from ${tool.server})`);
134
+ debugLog(`[MCP] ✓ Tool '${tool.name}' registered`);
108
135
  }
109
136
 
110
137
  this.isInitialized = true;
138
+
139
+ const serverList = Array.from(this.mcpAgent.servers.keys());
140
+ debugLog(`[MCP] ========================================`);
141
+ debugLog(`[MCP] Integration initialized successfully!`);
142
+ debugLog(`[MCP] Servers: ${serverList.join(', ')}`);
143
+ debugLog(`[MCP] Tools: ${availableTools.length}`);
144
+ debugLog(`[MCP] ========================================`);
145
+
111
146
  return {
112
147
  success: true,
113
148
  toolCount: availableTools.length,
114
- servers: Array.from(this.mcpAgent.servers.keys())
149
+ servers: serverList
115
150
  };
116
151
 
117
152
  } catch (error) {
118
- debugLog(`MCP Integration initialization failed: ${error.message}`);
119
- debugLog(' Continuing without MCP functionality.');
153
+ debugLog(`[MCP] ========================================`);
154
+ debugLog(`[MCP] Integration initialization FAILED`);
155
+ debugLog(`[MCP] Error: ${error.message}`);
156
+ debugLog(`[MCP] Stack: ${error.stack}`);
157
+ debugLog(`[MCP] Continuing without MCP functionality.`);
158
+ debugLog(`[MCP] ========================================`);
120
159
  return { success: false, reason: 'initialization_error', error: error.message };
121
160
  }
122
161
  }
123
162
 
124
163
  /**
125
- * MCP 도구 목록 반환 (기존 toolMap에 통합하기 위한 형식)
164
+ * MCP 도구 목록을 실행 가능한 함수 형태로 반환
165
+ * AI Agent의 기존 toolMap에 통합하기 위한 형식
126
166
  */
127
167
  getToolFunctions() {
128
168
  const toolFunctions = {};
129
169
 
130
170
  for (const [toolName, toolInfo] of this.mcpTools) {
171
+ // 각 도구에 대한 실행 함수 생성
131
172
  toolFunctions[toolName] = async (args) => {
132
173
  try {
174
+ // MCP Agent를 통해 도구 실행 (MCP 프로토콜로 서버와 통신)
133
175
  const result = await toolInfo.execute(args);
134
176
 
135
- // MCP 결과를 기존 시스템 형식으로 변환
177
+ // MCP 서버의 응답을 AI Agent 시스템의 표준 형식으로 변환
136
178
  if (result.success) {
137
179
  return {
138
180
  operation_successful: true,
@@ -164,20 +206,21 @@ export class MCPIntegration {
164
206
  }
165
207
 
166
208
  /**
167
- * MCP 도구 스키마 목록 반환 (Orchestrator에 전달하기 위한 형식)
209
+ * MCP 도구 스키마 목록을 OpenAI API 형식으로 반환
210
+ * Orchestrator가 AI 모델에게 사용 가능한 도구 목록을 전달할 때 사용
168
211
  */
169
212
  getToolSchemas() {
170
213
  const schemas = [];
171
214
 
172
215
  for (const [toolName, toolInfo] of this.mcpTools) {
173
- // OpenAI API strict mode 요구사항에 맞게 스키마 변환
216
+ // MCP 서버가 제공한 inputSchema를 OpenAI function calling 형식으로 변환
174
217
  const inputSchema = toolInfo.schema.inputSchema || {};
175
218
  const properties = inputSchema.properties || {};
176
219
  const originalRequired = inputSchema.required || [];
177
220
 
178
- // strict: true일 때는 required에 모든 properties 키가 포함되어야 함
179
- // 하지만 이는 너무 엄격하므로, strict: false로 설정하거나
180
- // additionalProperties: false를 추가하되 required는 원본 유지
221
+ // OpenAI API의 function calling 스키마 형식으로 변환
222
+ // strict: true는 모든 properties가 required에 포함되어야 하는 제약이 있어
223
+ // MCP 도구의 유연성을 위해 strict: false로 설정
181
224
  const cleanedSchema = {
182
225
  name: toolInfo.schema.name,
183
226
  description: toolInfo.schema.description,
@@ -219,21 +262,29 @@ export class MCPIntegration {
219
262
  }
220
263
 
221
264
  /**
222
- * MCP Agent 정리 (프로그램 종료 호출)
265
+ * MCP Agent 정리 모든 서버 연결 종료
266
+ * 프로그램 종료 시 호출되어 리소스를 정리
223
267
  */
224
268
  async cleanup() {
225
269
  if (this.mcpAgent) {
226
270
  try {
227
- // mcp-agent-lib는 cleanup() 메서드를 사용
271
+ debugLog('[MCP] Starting cleanup process...');
272
+ // mcp-agent-lib 버전에 따라 cleanup() 또는 disconnect() 메서드 호출
273
+ // 모든 MCP 서버와의 연결을 정상적으로 종료
228
274
  if (typeof this.mcpAgent.cleanup === 'function') {
275
+ debugLog('[MCP] Calling mcpAgent.cleanup()...');
229
276
  await this.mcpAgent.cleanup();
230
277
  } else if (typeof this.mcpAgent.disconnect === 'function') {
278
+ debugLog('[MCP] Calling mcpAgent.disconnect()...');
231
279
  await this.mcpAgent.disconnect();
232
280
  }
233
- debugLog('MCP Agent connection terminated');
281
+ debugLog('[MCP] All MCP server connections terminated successfully');
234
282
  } catch (error) {
235
- debugLog(`Error during MCP Agent termination: ${error.message}`);
283
+ debugLog(`[MCP] Error during cleanup: ${error.message}`);
284
+ debugLog(`[MCP] Stack: ${error.stack}`);
236
285
  }
286
+ } else {
287
+ debugLog('[MCP] No MCP Agent to cleanup (never initialized)');
237
288
  }
238
289
  }
239
290
  }
@@ -241,10 +292,13 @@ export class MCPIntegration {
241
292
  /**
242
293
  * 전역 MCP Integration 인스턴스 생성 헬퍼
243
294
  */
244
- export async function initializeMCPIntegration(projectRoot = process.cwd()) {
245
- const integration = new MCPIntegration(projectRoot);
295
+ export async function initializeMCPIntegration() {
296
+ debugLog('[MCP] initializeMCPIntegration() called');
297
+ const integration = new MCPIntegration();
246
298
  const result = await integration.initialize();
247
299
 
300
+ debugLog(`[MCP] initializeMCPIntegration() complete - success: ${result?.success}, reason: ${result?.reason || 'N/A'}`);
301
+
248
302
  // 서버가 없어도 객체는 반환 (빈 서버 목록으로 동작)
249
303
  return integration;
250
304
  }
@@ -32,6 +32,9 @@ export async function read_file({ filePath }) {
32
32
  debugLog(` - filePath starts with '../': ${filePath?.startsWith('../') || false}`);
33
33
  debugLog(` - Current Working Directory: ${process.cwd()}`);
34
34
 
35
+ // Intentional delay for testing pending state
36
+ await new Promise(resolve => setTimeout(resolve, 13));
37
+
35
38
  try {
36
39
  // 경로를 절대경로로 정규화
37
40
  const absolutePath = resolve(filePath);
@@ -169,6 +172,9 @@ export async function read_file_range({ filePath, startLine, endLine }) {
169
172
  debugLog(` endLine: ${endLine}`);
170
173
  debugLog(` - Current Working Directory: ${process.cwd()}`);
171
174
 
175
+ // Intentional delay for testing pending state
176
+ await new Promise(resolve => setTimeout(resolve, 13));
177
+
172
178
  try {
173
179
  // 경로를 절대경로로 정규화
174
180
  const absolutePath = resolve(filePath);
package/src/tools/glob.js CHANGED
@@ -40,6 +40,9 @@ export async function globSearch({
40
40
  debugLog(` maxResults: ${maxResults}`);
41
41
  debugLog(` - Current Working Directory: ${process.cwd()}`);
42
42
 
43
+ // Intentional delay for testing pending state
44
+ await new Promise(resolve => setTimeout(resolve, 13));
45
+
43
46
  try {
44
47
  if (typeof pattern !== 'string' || !pattern.trim()) {
45
48
  debugLog(`ERROR: Invalid pattern`);
@@ -332,6 +332,8 @@ export async function ripgrep({
332
332
  head_limit: headLimit = null,
333
333
  includeHidden = false
334
334
  }) {
335
+ // Intentional delay for testing pending state
336
+ await new Promise(resolve => setTimeout(resolve, 13));
335
337
 
336
338
  if (typeof pattern !== 'string' || !pattern.trim()) {
337
339
  return {
@@ -19,6 +19,9 @@ import { theme } from '../frontend/design/themeColors.js';
19
19
  * @returns {Promise<{operation_successful: boolean, url: string, content: string, error_message: string|null}>} 결과 객체
20
20
  */
21
21
  export async function fetch_web_page({ url }) {
22
+ // Intentional delay for testing pending state
23
+ await new Promise(resolve => setTimeout(resolve, 13));
24
+
22
25
  const timeout = 30;
23
26
  const user_agent = 'Mozilla/5.0 (compatible; WebFetcher/1.0)';
24
27
  const encoding = 'utf-8';