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.
- package/package.json +2 -1
- package/payload_viewer/out/404/index.html +1 -1
- package/payload_viewer/out/404.html +1 -1
- package/payload_viewer/out/index.html +1 -1
- package/payload_viewer/out/index.txt +1 -1
- package/prompts/completion_judge.txt +26 -0
- package/prompts/orchestrator.txt +172 -15
- package/src/ai_based/completion_judge.js +12 -1
- package/src/ai_based/orchestrator.js +23 -1
- package/src/cli/mcp_cli.js +1 -1
- package/src/frontend/App.js +21 -0
- package/src/frontend/components/TodoList.js +56 -0
- package/src/frontend/design/themeColors.js +4 -4
- package/src/system/session.js +53 -12
- package/src/system/session_memory.js +53 -2
- package/src/system/tool_registry.js +5 -2
- package/src/system/ui_events.js +10 -0
- package/src/tools/code_editor.js +2 -2
- package/src/tools/file_reader.js +6 -2
- package/src/tools/ripgrep.js +143 -50
- package/src/tools/todo_write.js +182 -0
- package/src/util/mcp_config_manager.js +1 -1
- package/src/util/prompt_loader.js +12 -4
- /package/payload_viewer/out/_next/static/{TvjwwIk8VOeA22CIg55Bx → 6yTW1SraROIP1ebN-kxTS}/_buildManifest.js +0 -0
- /package/payload_viewer/out/_next/static/{TvjwwIk8VOeA22CIg55Bx → 6yTW1SraROIP1ebN-kxTS}/_clientMiddlewareManifest.json +0 -0
- /package/payload_viewer/out/_next/static/{TvjwwIk8VOeA22CIg55Bx → 6yTW1SraROIP1ebN-kxTS}/_ssgManifest.js +0 -0
package/src/cli/mcp_cli.js
CHANGED
package/src/frontend/App.js
CHANGED
|
@@ -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: '#
|
|
10
|
-
link: '#
|
|
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: '#
|
|
32
|
-
light: '#
|
|
31
|
+
dark: '#113429',
|
|
32
|
+
light: '#47c9a0',
|
|
33
33
|
},
|
|
34
34
|
};
|
|
35
35
|
|
package/src/system/session.js
CHANGED
|
@@ -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
|
-
//
|
|
610
|
+
// Todo list 확인 - pending 상태의 todo가 있으면 자동으로 계속 진행
|
|
611
|
+
const currentTodos = getCurrentTodos();
|
|
612
|
+
const hasPendingTodos = currentTodos.some(todo => todo.status === 'pending');
|
|
613
|
+
|
|
610
614
|
let judgement;
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
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
|
}
|
package/src/system/ui_events.js
CHANGED
|
@@ -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
|
// 전역 인스턴스
|
package/src/tools/code_editor.js
CHANGED
|
@@ -247,7 +247,7 @@ export const writeFileSchema = {
|
|
|
247
247
|
};
|
|
248
248
|
|
|
249
249
|
/**
|
|
250
|
-
* 파일에서 정확한 문자열 매칭을 통해 내용을 교체합니다
|
|
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
|
|
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",
|
package/src/tools/file_reader.js
CHANGED
|
@@ -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
|
-
|
|
245
|
-
file_content: selectedLines.join('\n')
|
|
249
|
+
file_content: contentWithLineNumbers
|
|
246
250
|
};
|
|
247
251
|
} catch (error) {
|
|
248
252
|
debugLog(`========== read_file_range EXCEPTION ==========`);
|