aiexecode 1.0.70 → 1.0.72

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.

@@ -1,4 +1,4 @@
1
- // MCP CLI 명령어 등록 - Claude Code 스타일
1
+ // MCP CLI 명령어 등록
2
2
  import { Command } from 'commander';
3
3
  import {
4
4
  addMcpServer,
@@ -11,6 +11,7 @@ import { Input } from './components/Input.js';
11
11
  import { SessionSpinner } from './components/SessionSpinner.js';
12
12
  import { ConversationItem } from './components/ConversationItem.js';
13
13
  import { BlankLine } from './components/BlankLine.js';
14
+ import { TodoList } from './components/TodoList.js';
14
15
  import { useTextBuffer } from './utils/inputBuffer.js';
15
16
  import { uiEvents } from '../system/ui_events.js';
16
17
  import { getToolDisplayConfig, extractMessageFromArgs, formatToolCall, formatToolResult, getToolDisplayName } from '../system/tool_registry.js';
@@ -348,6 +349,7 @@ export function App({ onSubmit, onClearScreen, onExit, commands = [], model, ver
348
349
  const [sessionMessage, setSessionMessage] = useState('Processing...');
349
350
  const [approvalRequest, setApprovalRequest] = useState(null);
350
351
  const [showSetupWizard, setShowSetupWizard] = useState(false);
352
+ const [todos, setTodos] = useState([]);
351
353
 
352
354
  // Static items: 메모이제이션된 React 엘리먼트들 (Static 컴포넌트 제거로 스크롤 문제 해결)
353
355
  const [staticItems, setStaticItems] = useState(() => [
@@ -389,6 +391,13 @@ export function App({ onSubmit, onClearScreen, onExit, commands = [], model, ver
389
391
  if (!text.trim().startsWith('/')) {
390
392
  const userEvent = { type: 'user', text };
391
393
 
394
+ // Clear todos only if all are completed
395
+ setTodos(prev => {
396
+ if (prev.length === 0) return [];
397
+ const allCompleted = prev.every(todo => todo.status === 'completed');
398
+ return allCompleted ? [] : prev;
399
+ });
400
+
392
401
  // history에 추가
393
402
  setHistory(prev => [...prev, userEvent]);
394
403
 
@@ -870,6 +879,13 @@ export function App({ onSubmit, onClearScreen, onExit, commands = [], model, ver
870
879
  setShowSetupWizard(true);
871
880
  };
872
881
 
882
+ const handleTodosUpdate = (event) => {
883
+ if (event.todos) {
884
+ debugLog(`[handleTodosUpdate] Updating todos: ${event.todos.length} items`);
885
+ setTodos(event.todos);
886
+ }
887
+ };
888
+
873
889
  uiEvents.on('history:add', handleHistoryAdd);
874
890
  uiEvents.on('session:state', handleSessionState);
875
891
  uiEvents.on('screen:clear', handleClearScreen);
@@ -877,6 +893,7 @@ export function App({ onSubmit, onClearScreen, onExit, commands = [], model, ver
877
893
  uiEvents.on('model:changed', handleModelChanged);
878
894
  uiEvents.on('reasoning_effort:changed', handleReasoningEffortChanged);
879
895
  uiEvents.on('setup:show', handleSetupShow);
896
+ uiEvents.on('todos:update', handleTodosUpdate);
880
897
 
881
898
  return () => {
882
899
  uiEvents.off('history:add', handleHistoryAdd);
@@ -886,6 +903,7 @@ export function App({ onSubmit, onClearScreen, onExit, commands = [], model, ver
886
903
  uiEvents.off('model:changed', handleModelChanged);
887
904
  uiEvents.off('reasoning_effort:changed', handleReasoningEffortChanged);
888
905
  uiEvents.off('setup:show', handleSetupShow);
906
+ uiEvents.off('todos:update', handleTodosUpdate);
889
907
  };
890
908
  }, [addToHistory, handleSessionTransition]);
891
909
 
@@ -979,6 +997,9 @@ export function App({ onSubmit, onClearScreen, onExit, commands = [], model, ver
979
997
  isRunning: isSessionRunning,
980
998
  message: sessionMessage
981
999
  }),
1000
+ !approvalRequest && todos.length > 0 && React.createElement(TodoList, {
1001
+ todos: todos
1002
+ }),
982
1003
  !approvalRequest && React.createElement(Input, {
983
1004
  buffer,
984
1005
  onSubmit: handleSubmit,
@@ -0,0 +1,56 @@
1
+ /**
2
+ * TodoList Component
3
+ * Displays the current task list with status indicators
4
+ */
5
+
6
+ import React from 'react';
7
+ import { Box, Text } from 'ink';
8
+ import { theme } from '../design/themeColors.js';
9
+
10
+ const getStatusIcon = (status) => {
11
+ switch (status) {
12
+ case 'completed':
13
+ return '✓';
14
+ case 'in_progress':
15
+ return '→';
16
+ case 'pending':
17
+ return '○';
18
+ default:
19
+ return '?';
20
+ }
21
+ };
22
+
23
+ const getStatusColor = (status) => {
24
+ switch (status) {
25
+ case 'completed':
26
+ return theme.brand.light;
27
+ case 'in_progress':
28
+ return theme.brand.light;
29
+ case 'pending':
30
+ return theme.brand.dark;
31
+ default:
32
+ return theme.text.primary;
33
+ }
34
+ };
35
+
36
+ export function TodoList({ todos }) {
37
+ if (!todos || todos.length === 0) {
38
+ return null;
39
+ }
40
+
41
+ return React.createElement(Box, { flexDirection: "column", marginTop: 1, marginBottom: 1 },
42
+ React.createElement(Box, null,
43
+ React.createElement(Text, { bold: true, color: theme.brand.light }, '• Tasks:')
44
+ ),
45
+ ...todos.map((todo, index) =>
46
+ React.createElement(Box, { key: index, marginLeft: 2 },
47
+ React.createElement(Text, {
48
+ color: getStatusColor(todo.status),
49
+ strikethrough: todo.status === 'completed'
50
+ },
51
+ `${getStatusIcon(todo.status)} ${todo.status === 'in_progress' ? todo.activeForm : todo.content}`
52
+ )
53
+ )
54
+ )
55
+ );
56
+ }
@@ -6,8 +6,8 @@ const defaultTheme = {
6
6
  text: {
7
7
  primary: '#E0E0E0',
8
8
  secondary: '#888888',
9
- accent: '#3fbe96',
10
- link: '#3fbe96',
9
+ accent: '#47c9a0',
10
+ link: '#47c9a0',
11
11
  },
12
12
  background: {
13
13
  default: '#1E1E1E',
@@ -28,8 +28,8 @@ const defaultTheme = {
28
28
  info: '#00A8FF',
29
29
  },
30
30
  brand: {
31
- dark: '#418972',
32
- light: '#3fbe96',
31
+ dark: '#113429',
32
+ light: '#47c9a0',
33
33
  },
34
34
  };
35
35
 
@@ -8,9 +8,10 @@ import { GLOB_FUNCTIONS } from "../tools/glob.js";
8
8
  import { CODE_EDITOR_FUNCTIONS } from "../tools/code_editor.js";
9
9
  import { WEB_DOWNLOADER_FUNCTIONS } from "../tools/web_downloader.js";
10
10
  import { RESPONSE_MESSAGE_FUNCTIONS } from '../tools/response_message.js';
11
+ import { TODO_WRITE_FUNCTIONS } from '../tools/todo_write.js';
11
12
  import { clampOutput, formatToolStdout } from "../util/output_formatter.js";
12
13
  import { buildToolHistoryEntry } from "../util/rag_helper.js";
13
- import { createSessionData, getLastConversationState, loadPreviousSessions, saveSessionToHistory } from "./session_memory.js";
14
+ import { createSessionData, getLastConversationState, loadPreviousSessions, saveSessionToHistory, saveTodosToSession, restoreTodosFromSession, updateCurrentTodos, getCurrentTodos } from "./session_memory.js";
14
15
  import { uiEvents } from "./ui_events.js";
15
16
  import { logSystem, logError, logAssistantMessage, logToolCall, logToolResult, logCodeExecution, logCodeResult, logIteration, logMissionComplete, logConversationRestored } from "./output_helper.js";
16
17
  import { requiresApproval, requestApproval } from "./tool_approval.js";
@@ -606,19 +607,32 @@ async function processOrchestratorResponses(params) {
606
607
  break;
607
608
  }
608
609
 
609
- // LLM을 통해 실제 완료 여부 판단
610
+ // Todo list 확인 - pending 상태의 todo가 있으면 자동으로 계속 진행
611
+ const currentTodos = getCurrentTodos();
612
+ const hasPendingTodos = currentTodos.some(todo => todo.status === 'pending');
613
+
610
614
  let judgement;
611
- try {
612
- judgement = await judgeMissionCompletion({
613
- what_user_requests: mission
614
- });
615
- completionJudgeExecuted = true; // completion_judge 실행 완료
616
- } catch (err) {
617
- if (err.name === 'AbortError' || sessionInterrupted) {
618
- debugLog(`judgeMissionCompletion aborted or interrupted: ${err.name}`);
619
- break;
615
+
616
+ if (currentTodos.length > 0 && hasPendingTodos) {
617
+ debugLog(`Found ${currentTodos.length} todos with ${currentTodos.filter(t => t.status === 'pending').length} pending items - skipping completion_judge`);
618
+ judgement = {
619
+ shouldComplete: false,
620
+ whatUserShouldSay: "continue"
621
+ };
622
+ } else {
623
+ // LLM을 통해 실제 완료 여부 판단
624
+ try {
625
+ judgement = await judgeMissionCompletion({
626
+ what_user_requests: mission
627
+ });
628
+ completionJudgeExecuted = true; // completion_judge 실행 완료
629
+ } catch (err) {
630
+ if (err.name === 'AbortError' || sessionInterrupted) {
631
+ debugLog(`judgeMissionCompletion aborted or interrupted: ${err.name}`);
632
+ break;
633
+ }
634
+ throw err;
620
635
  }
621
- throw err;
622
636
  }
623
637
 
624
638
  // 세션 중단 확인 (completion_judge 호출 후)
@@ -845,10 +859,33 @@ export async function runSession(options) {
845
859
  conversationState.orchestratorConversation,
846
860
  conversationState.orchestratorRequestOptions
847
861
  );
862
+ // Todos 복원
863
+ if (conversationState.currentTodos) {
864
+ restoreTodosFromSession({ currentTodos: conversationState.currentTodos });
865
+ debugLog(`[runSession] Restored ${conversationState.currentTodos.length} todos from previous session`);
866
+ }
848
867
  // logSuccess('✓ Conversation state restored');
849
868
  } else {
850
869
  // logSystem('ℹ Starting with fresh conversation state');
851
870
  resetOrchestratorConversation();
871
+
872
+ // 현재 메모리의 todos 확인 (화면에 표시된 todos)
873
+ const currentTodos = getCurrentTodos();
874
+ if (currentTodos && currentTodos.length > 0) {
875
+ const allCompleted = currentTodos.every(todo => todo.status === 'completed');
876
+ if (allCompleted) {
877
+ // 모두 완료되었으면 클리어
878
+ updateCurrentTodos([]);
879
+ debugLog(`[runSession] Starting fresh session - all ${currentTodos.length} todos completed, cleared`);
880
+ } else {
881
+ // 완료되지 않은 todos가 있으면 유지 (아무 작업도 하지 않음)
882
+ debugLog(`[runSession] Starting fresh conversation but keeping ${currentTodos.length} incomplete todos`);
883
+ }
884
+ } else {
885
+ // Todos가 없으면 명시적으로 클리어
886
+ updateCurrentTodos([]);
887
+ debugLog(`[runSession] Starting fresh session - no todos in memory`);
888
+ }
852
889
  }
853
890
 
854
891
  // Python 사용 가능 여부 확인
@@ -861,6 +898,7 @@ export async function runSession(options) {
861
898
  ...RIPGREP_FUNCTIONS,
862
899
  ...GLOB_FUNCTIONS,
863
900
  ...RESPONSE_MESSAGE_FUNCTIONS,
901
+ ...TODO_WRITE_FUNCTIONS,
864
902
  ...mcpToolFunctions,
865
903
  "bash": async (args) => execShellScript(args.script)
866
904
  };
@@ -989,6 +1027,9 @@ export async function runSession(options) {
989
1027
  currentSessionData.orchestratorConversation = getOrchestratorConversation();
990
1028
  currentSessionData.orchestratorRequestOptions = null;
991
1029
 
1030
+ // Todos를 세션에 저장
1031
+ saveTodosToSession(currentSessionData);
1032
+
992
1033
  debugLog(`[ITERATION ${iteration_count}] Saving session to history after completion_judge (mission_solved=${mission_solved})`);
993
1034
  await saveSessionToHistory(currentSessionData).catch(err => {
994
1035
  debugLog(`[ITERATION ${iteration_count}] Failed to save session after completion_judge: ${err.message}`);
@@ -129,7 +129,9 @@ export function createSessionData(sessionID, mission) {
129
129
  toolUsageHistory: [],
130
130
  // 대화 상태 저장
131
131
  orchestratorConversation: [],
132
- orchestratorRequestOptions: null
132
+ orchestratorRequestOptions: null,
133
+ // Todo 리스트 저장
134
+ currentTodos: []
133
135
  };
134
136
  }
135
137
 
@@ -147,7 +149,8 @@ export function getLastConversationState(sessions) {
147
149
 
148
150
  return {
149
151
  orchestratorConversation: lastSession.orchestratorConversation || [],
150
- orchestratorRequestOptions: lastSession.orchestratorRequestOptions || null
152
+ orchestratorRequestOptions: lastSession.orchestratorRequestOptions || null,
153
+ currentTodos: lastSession.currentTodos || []
151
154
  };
152
155
  }
153
156
 
@@ -399,3 +402,51 @@ export function reconstructUIHistory(sessions) {
399
402
 
400
403
  return uiHistory;
401
404
  }
405
+
406
+ /**
407
+ * 현재 세션의 todos를 메모리에 저장하기 위한 전역 변수
408
+ */
409
+ let currentSessionTodos = [];
410
+
411
+ /**
412
+ * 현재 세션의 todos를 업데이트
413
+ * @param {Array} todos - Todo 항목 배열
414
+ */
415
+ export function updateCurrentTodos(todos) {
416
+ debugLog('========== updateCurrentTodos ==========');
417
+ debugLog(`Updating todos: ${todos.length} items`);
418
+ currentSessionTodos = todos;
419
+ }
420
+
421
+ /**
422
+ * 현재 세션의 todos를 가져옴
423
+ * @returns {Array} Todo 항목 배열
424
+ */
425
+ export function getCurrentTodos() {
426
+ return currentSessionTodos;
427
+ }
428
+
429
+ /**
430
+ * 세션 데이터에 현재 todos를 저장
431
+ * @param {Object} sessionData - 세션 데이터 객체
432
+ */
433
+ export function saveTodosToSession(sessionData) {
434
+ debugLog('========== saveTodosToSession ==========');
435
+ debugLog(`Saving ${currentSessionTodos.length} todos to session`);
436
+ sessionData.currentTodos = [...currentSessionTodos];
437
+ }
438
+
439
+ /**
440
+ * 세션 데이터에서 todos를 복원
441
+ * @param {Object} sessionData - 세션 데이터 객체
442
+ */
443
+ export function restoreTodosFromSession(sessionData) {
444
+ debugLog('========== restoreTodosFromSession ==========');
445
+ if (sessionData && sessionData.currentTodos) {
446
+ debugLog(`Restoring ${sessionData.currentTodos.length} todos from session`);
447
+ currentSessionTodos = [...sessionData.currentTodos];
448
+ } else {
449
+ debugLog('No todos to restore');
450
+ currentSessionTodos = [];
451
+ }
452
+ }
@@ -8,6 +8,7 @@ import { GLOB_FUNCTIONS, globSearchSchema } from "../tools/glob.js";
8
8
  import { CODE_EDITOR_FUNCTIONS, writeFileSchema, editFileRangeSchema, editFileReplaceSchema } from "../tools/code_editor.js";
9
9
  import { WEB_DOWNLOADER_FUNCTIONS, fetchWebPageSchema } from "../tools/web_downloader.js";
10
10
  import { RESPONSE_MESSAGE_FUNCTIONS, responseMessageSchema } from '../tools/response_message.js';
11
+ import { TODO_WRITE_FUNCTIONS, todoWriteSchema } from '../tools/todo_write.js';
11
12
  import { runPythonCodeSchema, bashSchema } from '../system/code_executer.js';
12
13
 
13
14
  // 도구 스키마 레지스트리
@@ -22,7 +23,8 @@ const TOOL_SCHEMAS = {
22
23
  'fetch_web_page': fetchWebPageSchema,
23
24
  'ripgrep': ripgrepSchema,
24
25
  'run_python_code': runPythonCodeSchema,
25
- 'bash': bashSchema
26
+ 'bash': bashSchema,
27
+ 'todo_write': todoWriteSchema
26
28
  };
27
29
 
28
30
  /**
@@ -162,6 +164,7 @@ export function getAllToolFunctions() {
162
164
  ...RIPGREP_FUNCTIONS,
163
165
  ...GLOB_FUNCTIONS,
164
166
  ...WEB_DOWNLOADER_FUNCTIONS,
165
- ...RESPONSE_MESSAGE_FUNCTIONS
167
+ ...RESPONSE_MESSAGE_FUNCTIONS,
168
+ ...TODO_WRITE_FUNCTIONS
166
169
  };
167
170
  }
@@ -221,6 +221,16 @@ class UIEventEmitter extends EventEmitter {
221
221
  });
222
222
  });
223
223
  }
224
+
225
+ /**
226
+ * Todo 리스트 업데이트
227
+ */
228
+ updateTodos(todos) {
229
+ this.emit('todos:update', {
230
+ todos,
231
+ timestamp: Date.now()
232
+ });
233
+ }
224
234
  }
225
235
 
226
236
  // 전역 인스턴스
@@ -247,7 +247,7 @@ export const writeFileSchema = {
247
247
  };
248
248
 
249
249
  /**
250
- * 파일에서 정확한 문자열 매칭을 통해 내용을 교체합니다 (Claude Code 스타일)
250
+ * 파일에서 정확한 문자열 매칭을 통해 내용을 교체합니다
251
251
  *
252
252
  * @param {Object} params - 매개변수 객체
253
253
  * @param {string} params.file_path - 편집할 파일의 경로
@@ -681,7 +681,7 @@ export async function edit_file_range({ file_path, start_line, end_line, new_con
681
681
  // edit_file_replace 스키마
682
682
  export const editFileReplaceSchema = {
683
683
  "name": "edit_file_replace",
684
- "description": "Performs exact string replacements in files (Claude Code style).\n\nUSAGE:\n- You must have read the file at least once before editing. This tool will error if you attempt an edit without reading the file.\n- When editing text from file reader output, ensure you preserve the exact indentation (tabs/spaces) as it appears AFTER the line number prefix. Never include any part of the line number prefix in the old_string or new_string.\n- The edit will FAIL if old_string is not unique in the file. Provide a larger string with more surrounding context to make it unique.\n\nTOKEN EFFICIENCY - CRITICAL: MINIMIZE old_string length.\n- Include ONLY the minimum code needed to uniquely identify the change location\n- Start with just the line(s) you want to modify\n- If rejected with 'not unique' error, incrementally add surrounding context\n- Strategy: (1) Try minimal old_string first (2) If rejected, add 1-2 lines context (3) Repeat until unique\n- Balance: Too short = not unique, Too long = wastes tokens\n- Example WASTEFUL: Including 30 lines when 1 line would be unique\n- Example EFFICIENT: \"const tax = 0.1;\" (if unique)\n- Example EFFICIENT: \"function calc() {\\n const tax = 0.1;\\n}\" (when context needed)\n\nCONTENT PURITY - CRITICAL: old_string and new_string must be EXACT file content. Include ONLY the actual code - NO explanatory text, NO markdown blocks, NO instructions.\n\nWRONG: \"Here's the code:\\nfunction foo() {}\" or \"```javascript\\ncode\\n```\"\nCORRECT: \"function foo() {\\n return true;\\n}\"",
684
+ "description": "Performs exact string replacements in files.\n\nUSAGE:\n- You must have read the file at least once before editing. This tool will error if you attempt an edit without reading the file.\n- When editing text from file reader output, ensure you preserve the exact indentation (tabs/spaces) as it appears AFTER the line number prefix. Never include any part of the line number prefix in the old_string or new_string.\n- The edit will FAIL if old_string is not unique in the file. Provide a larger string with more surrounding context to make it unique.\n\nTOKEN EFFICIENCY - CRITICAL: MINIMIZE old_string length.\n- Include ONLY the minimum code needed to uniquely identify the change location\n- Start with just the line(s) you want to modify\n- If rejected with 'not unique' error, incrementally add surrounding context\n- Strategy: (1) Try minimal old_string first (2) If rejected, add 1-2 lines context (3) Repeat until unique\n- Balance: Too short = not unique, Too long = wastes tokens\n- Example WASTEFUL: Including 30 lines when 1 line would be unique\n- Example EFFICIENT: \"const tax = 0.1;\" (if unique)\n- Example EFFICIENT: \"function calc() {\\n const tax = 0.1;\\n}\" (when context needed)\n\nCONTENT PURITY - CRITICAL: old_string and new_string must be EXACT file content. Include ONLY the actual code - NO explanatory text, NO markdown blocks, NO instructions.\n\nWRONG: \"Here's the code:\\nfunction foo() {}\" or \"```javascript\\ncode\\n```\"\nCORRECT: \"function foo() {\\n return true;\\n}\"",
685
685
  "strict": false,
686
686
  "parameters": {
687
687
  "type": "object",
@@ -230,6 +230,11 @@ export async function read_file_range({ filePath, startLine, endLine }) {
230
230
  line_content: line
231
231
  }));
232
232
 
233
+ // file_content에 라인 번호 추가
234
+ const contentWithLineNumbers = selectedLines
235
+ .map((line, index) => `${startLine + index}| ${line}`)
236
+ .join('\n');
237
+
233
238
  debugLog('========== read_file_range SUCCESS END ==========');
234
239
 
235
240
  return {
@@ -241,8 +246,7 @@ export async function read_file_range({ filePath, startLine, endLine }) {
241
246
  start_line: startLine,
242
247
  end_line: Math.min(endLine, totalLines)
243
248
  },
244
- file_lines: numberedLines,
245
- file_content: selectedLines.join('\n')
249
+ file_content: contentWithLineNumbers
246
250
  };
247
251
  } catch (error) {
248
252
  debugLog(`========== read_file_range EXCEPTION ==========`);