aiexecode 1.0.94 → 1.0.127

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.

Files changed (80) hide show
  1. package/README.md +198 -88
  2. package/index.js +310 -86
  3. package/mcp-agent-lib/src/mcp_message_logger.js +17 -16
  4. package/package.json +4 -4
  5. package/payload_viewer/out/404/index.html +1 -1
  6. package/payload_viewer/out/404.html +1 -1
  7. package/payload_viewer/out/_next/static/chunks/{37d0cd2587a38f79.js → b6c0459f3789d25c.js} +1 -1
  8. package/payload_viewer/out/_next/static/chunks/b75131b58f8ca46a.css +3 -0
  9. package/payload_viewer/out/index.html +1 -1
  10. package/payload_viewer/out/index.txt +3 -3
  11. package/payload_viewer/web_server.js +361 -0
  12. package/prompts/completion_judge.txt +4 -0
  13. package/prompts/orchestrator.txt +116 -3
  14. package/src/LLMClient/client.js +401 -18
  15. package/src/LLMClient/converters/responses-to-claude.js +67 -18
  16. package/src/LLMClient/converters/responses-to-zai.js +667 -0
  17. package/src/LLMClient/errors.js +30 -4
  18. package/src/LLMClient/index.js +5 -0
  19. package/src/ai_based/completion_judge.js +263 -186
  20. package/src/ai_based/orchestrator.js +171 -35
  21. package/src/commands/agents.js +70 -0
  22. package/src/commands/apikey.js +1 -1
  23. package/src/commands/bg.js +129 -0
  24. package/src/commands/commands.js +51 -0
  25. package/src/commands/debug.js +52 -0
  26. package/src/commands/help.js +11 -1
  27. package/src/commands/model.js +42 -7
  28. package/src/commands/reasoning_effort.js +2 -2
  29. package/src/commands/skills.js +46 -0
  30. package/src/config/ai_models.js +106 -6
  31. package/src/config/constants.js +71 -0
  32. package/src/config/feature_flags.js +6 -7
  33. package/src/frontend/App.js +108 -1
  34. package/src/frontend/components/AutocompleteMenu.js +7 -1
  35. package/src/frontend/components/BackgroundProcessList.js +175 -0
  36. package/src/frontend/components/ConversationItem.js +26 -10
  37. package/src/frontend/components/CurrentModelView.js +2 -2
  38. package/src/frontend/components/HelpView.js +106 -2
  39. package/src/frontend/components/Input.js +33 -11
  40. package/src/frontend/components/ModelListView.js +1 -1
  41. package/src/frontend/components/SetupWizard.js +51 -8
  42. package/src/frontend/hooks/useFileCompletion.js +467 -0
  43. package/src/frontend/utils/toolUIFormatter.js +261 -0
  44. package/src/system/agents_loader.js +289 -0
  45. package/src/system/ai_request.js +156 -12
  46. package/src/system/background_process.js +317 -0
  47. package/src/system/code_executer.js +496 -56
  48. package/src/system/command_parser.js +33 -3
  49. package/src/system/conversation_state.js +265 -0
  50. package/src/system/conversation_trimmer.js +132 -0
  51. package/src/system/custom_command_loader.js +386 -0
  52. package/src/system/file_integrity.js +73 -10
  53. package/src/system/log.js +10 -2
  54. package/src/system/output_helper.js +52 -9
  55. package/src/system/session.js +213 -58
  56. package/src/system/session_memory.js +30 -2
  57. package/src/system/skill_loader.js +318 -0
  58. package/src/system/system_info.js +254 -40
  59. package/src/system/tool_approval.js +10 -0
  60. package/src/system/tool_registry.js +15 -1
  61. package/src/system/ui_events.js +11 -0
  62. package/src/tools/code_editor.js +16 -10
  63. package/src/tools/file_reader.js +66 -9
  64. package/src/tools/glob.js +0 -3
  65. package/src/tools/ripgrep.js +5 -7
  66. package/src/tools/skill_tool.js +122 -0
  67. package/src/tools/web_downloader.js +0 -3
  68. package/src/util/clone.js +174 -0
  69. package/src/util/config.js +55 -2
  70. package/src/util/config_migration.js +174 -0
  71. package/src/util/debug_log.js +8 -2
  72. package/src/util/exit_handler.js +8 -0
  73. package/src/util/file_reference_parser.js +132 -0
  74. package/src/util/path_validator.js +178 -0
  75. package/src/util/prompt_loader.js +91 -1
  76. package/src/util/safe_fs.js +66 -3
  77. package/payload_viewer/out/_next/static/chunks/ecd2072ebf41611f.css +0 -3
  78. /package/payload_viewer/out/_next/static/{wkEKh6i9XPSyP6rjDRvHn → 42iEoi-1o5MxNIZ1SWSvV}/_buildManifest.js +0 -0
  79. /package/payload_viewer/out/_next/static/{wkEKh6i9XPSyP6rjDRvHn → 42iEoi-1o5MxNIZ1SWSvV}/_clientMiddlewareManifest.json +0 -0
  80. /package/payload_viewer/out/_next/static/{wkEKh6i9XPSyP6rjDRvHn → 42iEoi-1o5MxNIZ1SWSvV}/_ssgManifest.js +0 -0
@@ -0,0 +1,265 @@
1
+ /**
2
+ * Conversation State Manager
3
+ * orchestratorConversation 전역 변수를 클래스로 캡슐화하여 상태 관리를 개선합니다.
4
+ */
5
+
6
+ import { deepClone } from '../util/clone.js';
7
+
8
+ /**
9
+ * 대화 상태 관리 클래스
10
+ * 전역 대화 상태를 캡슐화하고 안전한 접근 메서드를 제공합니다.
11
+ */
12
+ class ConversationStateManager {
13
+ constructor() {
14
+ /** @type {Array} 대화 히스토리 */
15
+ this._conversation = [];
16
+
17
+ /** @type {Object|null} 요청 옵션 */
18
+ this._requestOptions = null;
19
+
20
+ /** @type {number} 마지막 수정 타임스탬프 */
21
+ this._lastModified = Date.now();
22
+
23
+ /** @type {string|null} 현재 세션 ID */
24
+ this._sessionId = null;
25
+ }
26
+
27
+ /**
28
+ * 대화 히스토리를 가져옵니다.
29
+ * @param {boolean} clone - true이면 깊은 복사본 반환 (기본: false)
30
+ * @returns {Array} 대화 히스토리
31
+ */
32
+ getConversation(clone = false) {
33
+ if (clone) {
34
+ return deepClone(this._conversation);
35
+ }
36
+ return this._conversation;
37
+ }
38
+
39
+ /**
40
+ * 대화 히스토리를 설정합니다.
41
+ * @param {Array} conversation - 새 대화 히스토리
42
+ */
43
+ setConversation(conversation) {
44
+ if (!Array.isArray(conversation)) {
45
+ throw new Error('Conversation must be an array');
46
+ }
47
+ this._conversation = conversation;
48
+ this._lastModified = Date.now();
49
+ }
50
+
51
+ /**
52
+ * 대화에 메시지를 추가합니다.
53
+ * @param {Object} message - 추가할 메시지
54
+ */
55
+ addMessage(message) {
56
+ if (!message || typeof message !== 'object') {
57
+ throw new Error('Message must be an object');
58
+ }
59
+ this._conversation.push(message);
60
+ this._lastModified = Date.now();
61
+ }
62
+
63
+ /**
64
+ * 대화에 여러 메시지를 추가합니다.
65
+ * @param {Array} messages - 추가할 메시지 배열
66
+ */
67
+ addMessages(messages) {
68
+ if (!Array.isArray(messages)) {
69
+ throw new Error('Messages must be an array');
70
+ }
71
+ this._conversation.push(...messages);
72
+ this._lastModified = Date.now();
73
+ }
74
+
75
+ /**
76
+ * 대화 히스토리를 초기화합니다.
77
+ */
78
+ reset() {
79
+ this._conversation = [];
80
+ this._requestOptions = null;
81
+ this._lastModified = Date.now();
82
+ }
83
+
84
+ /**
85
+ * 대화 히스토리를 복원합니다.
86
+ * @param {Array} conversation - 복원할 대화 히스토리
87
+ * @param {Object} requestOptions - 복원할 요청 옵션
88
+ */
89
+ restore(conversation, requestOptions = null) {
90
+ this._conversation = Array.isArray(conversation) ? conversation : [];
91
+ this._requestOptions = requestOptions;
92
+ this._lastModified = Date.now();
93
+ }
94
+
95
+ /**
96
+ * 요청 옵션을 가져옵니다.
97
+ * @returns {Object|null} 요청 옵션
98
+ */
99
+ getRequestOptions() {
100
+ return this._requestOptions;
101
+ }
102
+
103
+ /**
104
+ * 요청 옵션을 설정합니다.
105
+ * @param {Object} options - 요청 옵션
106
+ */
107
+ setRequestOptions(options) {
108
+ this._requestOptions = options;
109
+ this._lastModified = Date.now();
110
+ }
111
+
112
+ /**
113
+ * 대화 히스토리의 길이를 반환합니다.
114
+ * @returns {number} 메시지 수
115
+ */
116
+ get length() {
117
+ return this._conversation.length;
118
+ }
119
+
120
+ /**
121
+ * 대화가 비어있는지 확인합니다.
122
+ * @returns {boolean} 비어있으면 true
123
+ */
124
+ isEmpty() {
125
+ return this._conversation.length === 0;
126
+ }
127
+
128
+ /**
129
+ * 마지막 수정 시간을 반환합니다.
130
+ * @returns {number} 타임스탬프
131
+ */
132
+ getLastModified() {
133
+ return this._lastModified;
134
+ }
135
+
136
+ /**
137
+ * 세션 ID를 설정합니다.
138
+ * @param {string} sessionId - 세션 ID
139
+ */
140
+ setSessionId(sessionId) {
141
+ this._sessionId = sessionId;
142
+ }
143
+
144
+ /**
145
+ * 세션 ID를 가져옵니다.
146
+ * @returns {string|null} 세션 ID
147
+ */
148
+ getSessionId() {
149
+ return this._sessionId;
150
+ }
151
+
152
+ /**
153
+ * 대화에서 특정 조건을 만족하는 메시지를 찾습니다.
154
+ * @param {Function} predicate - 검색 조건 함수
155
+ * @returns {Object|undefined} 찾은 메시지 또는 undefined
156
+ */
157
+ findMessage(predicate) {
158
+ return this._conversation.find(predicate);
159
+ }
160
+
161
+ /**
162
+ * 대화에서 특정 조건을 만족하는 메시지들을 필터링합니다.
163
+ * @param {Function} predicate - 필터 조건 함수
164
+ * @returns {Array} 필터링된 메시지 배열
165
+ */
166
+ filterMessages(predicate) {
167
+ return this._conversation.filter(predicate);
168
+ }
169
+
170
+ /**
171
+ * 마지막 메시지를 가져옵니다.
172
+ * @returns {Object|undefined} 마지막 메시지 또는 undefined
173
+ */
174
+ getLastMessage() {
175
+ if (this._conversation.length === 0) {
176
+ return undefined;
177
+ }
178
+ return this._conversation[this._conversation.length - 1];
179
+ }
180
+
181
+ /**
182
+ * 마지막 N개의 메시지를 가져옵니다.
183
+ * @param {number} count - 가져올 메시지 수
184
+ * @returns {Array} 메시지 배열
185
+ */
186
+ getLastMessages(count) {
187
+ if (count <= 0) {
188
+ return [];
189
+ }
190
+ return this._conversation.slice(-count);
191
+ }
192
+
193
+ /**
194
+ * 대화 히스토리를 트리밍합니다 (오래된 메시지 제거).
195
+ * @param {number} keepCount - 유지할 메시지 수 (시스템 메시지 제외)
196
+ * @returns {number} 제거된 메시지 수
197
+ */
198
+ trim(keepCount) {
199
+ const originalLength = this._conversation.length;
200
+
201
+ // 시스템 메시지는 유지
202
+ const systemMessages = this._conversation.filter(m => m.role === 'system');
203
+ const otherMessages = this._conversation.filter(m => m.role !== 'system');
204
+
205
+ // 최근 keepCount개만 유지
206
+ const keptMessages = otherMessages.slice(-keepCount);
207
+
208
+ this._conversation = [...systemMessages, ...keptMessages];
209
+ this._lastModified = Date.now();
210
+
211
+ return originalLength - this._conversation.length;
212
+ }
213
+
214
+ /**
215
+ * 상태를 직렬화 가능한 객체로 변환합니다.
216
+ * @returns {Object} 직렬화 가능한 상태 객체
217
+ */
218
+ toJSON() {
219
+ return {
220
+ conversation: deepClone(this._conversation),
221
+ requestOptions: this._requestOptions ? deepClone(this._requestOptions) : null,
222
+ lastModified: this._lastModified,
223
+ sessionId: this._sessionId
224
+ };
225
+ }
226
+
227
+ /**
228
+ * 직렬화된 상태에서 복원합니다.
229
+ * @param {Object} state - 직렬화된 상태 객체
230
+ */
231
+ fromJSON(state) {
232
+ if (state.conversation) {
233
+ this._conversation = state.conversation;
234
+ }
235
+ if (state.requestOptions !== undefined) {
236
+ this._requestOptions = state.requestOptions;
237
+ }
238
+ if (state.lastModified) {
239
+ this._lastModified = state.lastModified;
240
+ }
241
+ if (state.sessionId) {
242
+ this._sessionId = state.sessionId;
243
+ }
244
+ }
245
+ }
246
+
247
+ // 싱글톤 인스턴스 생성 및 export
248
+ export const conversationState = new ConversationStateManager();
249
+
250
+ // 후방 호환성을 위한 개별 함수들 export
251
+ export function getOrchestratorConversation() {
252
+ return conversationState.getConversation();
253
+ }
254
+
255
+ export function setOrchestratorConversation(conversation) {
256
+ conversationState.setConversation(conversation);
257
+ }
258
+
259
+ export function resetOrchestratorConversation() {
260
+ conversationState.reset();
261
+ }
262
+
263
+ export function restoreOrchestratorConversation(conversation, requestOptions) {
264
+ conversationState.restore(conversation, requestOptions);
265
+ }
@@ -6,9 +6,100 @@
6
6
  */
7
7
 
8
8
  import { createDebugLogger } from "../util/debug_log.js";
9
+ import { clearFileContentHash } from "./file_integrity.js";
9
10
 
10
11
  const debugLog = createDebugLogger('conversation_trimmer.log', 'conversation_trimmer');
11
12
 
13
+ /**
14
+ * Trim 과정에서 삭제된 파일 읽기 정보를 추적합니다.
15
+ * Claude Code 스타일의 "파일 경로 알림" 기능을 위해 사용됩니다.
16
+ *
17
+ * 예: "Note: /path/to/file was read before trimming, but contents are too large to include."
18
+ */
19
+ const trimmedFileReads = new Set();
20
+
21
+ /**
22
+ * trimmedFileReads의 최대 개수 제한
23
+ * 너무 많은 파일이 추적되면 system-reminder가 길어져 토큰 낭비 발생
24
+ */
25
+ const MAX_TRIMMED_FILE_READS = 50;
26
+
27
+ /**
28
+ * Trim된 파일 읽기 목록을 반환합니다.
29
+ * @returns {Array<string>} 삭제된 파일 경로 배열
30
+ */
31
+ export function getTrimmedFileReads() {
32
+ return Array.from(trimmedFileReads);
33
+ }
34
+
35
+ /**
36
+ * Trim된 파일 읽기 목록을 초기화합니다.
37
+ */
38
+ export function clearTrimmedFileReads() {
39
+ trimmedFileReads.clear();
40
+ debugLog(`[clearTrimmedFileReads] Cleared trimmed file reads`);
41
+ }
42
+
43
+ /**
44
+ * Trim된 파일 읽기 목록을 설정합니다 (세션 복원용).
45
+ * 최대 개수 제한을 적용합니다.
46
+ * @param {Array<string>} paths - 복원할 파일 경로 배열
47
+ */
48
+ export function setTrimmedFileReads(paths) {
49
+ trimmedFileReads.clear();
50
+ if (Array.isArray(paths)) {
51
+ // 최대 개수만큼만 추가 (최근 것 우선)
52
+ const pathsToAdd = paths.slice(-MAX_TRIMMED_FILE_READS);
53
+ pathsToAdd.forEach(path => trimmedFileReads.add(path));
54
+ debugLog(`[setTrimmedFileReads] Restored ${trimmedFileReads.size} trimmed file reads`);
55
+ }
56
+ }
57
+
58
+ /**
59
+ * 파일이 다시 읽혔을 때 trimmedFileReads에서 제거합니다.
60
+ * 파일을 다시 읽으면 대화에 내용이 있으므로 알림이 필요 없습니다.
61
+ * @param {string} filePath - 다시 읽은 파일 경로
62
+ */
63
+ export function markFileAsReRead(filePath) {
64
+ if (trimmedFileReads.has(filePath)) {
65
+ trimmedFileReads.delete(filePath);
66
+ debugLog(`[markFileAsReRead] Removed from trimmed file reads: ${filePath}`);
67
+ }
68
+ }
69
+
70
+ /**
71
+ * 대화 항목에서 파일 읽기 정보를 추출합니다.
72
+ * @param {Object} entry - 대화 항목
73
+ * @returns {Array<string>} 추출된 파일 경로 배열
74
+ */
75
+ function extractFilePathsFromEntry(entry) {
76
+ const filePaths = [];
77
+
78
+ if (!entry) return filePaths;
79
+
80
+ try {
81
+ // function_call_output에서 파일 읽기 결과 추출
82
+ if (entry.type === 'function_call_output' && entry.output) {
83
+ const output = typeof entry.output === 'string' ? JSON.parse(entry.output) : entry.output;
84
+
85
+ // read_file, read_file_range 도구의 결과인지 확인
86
+ if (output.tool === 'read_file' || output.tool === 'read_file_range') {
87
+ const result = output.original_result || output;
88
+ // 성공적으로 읽은 파일만 추적 (실패한 파일은 알림 불필요)
89
+ if (result.target_file_path && result.operation_successful === true) {
90
+ filePaths.push(result.target_file_path);
91
+ debugLog(`[extractFilePathsFromEntry] Found file path: ${result.target_file_path}`);
92
+ }
93
+ }
94
+ }
95
+ } catch (e) {
96
+ // JSON 파싱 실패 등은 무시
97
+ debugLog(`[extractFilePathsFromEntry] Failed to parse entry: ${e.message}`);
98
+ }
99
+
100
+ return filePaths;
101
+ }
102
+
12
103
  /**
13
104
  * 대화에서 특정 call_id와 관련된 모든 항목을 제거합니다.
14
105
  *
@@ -103,6 +194,9 @@ export function cleanupOrphanOutputs(conversation) {
103
194
  * 컨텍스트 윈도우 초과 시 대화를 줄입니다.
104
195
  * 시스템 프롬프트(인덱스 0)를 제외한 가장 오래된 항목(인덱스 1)을 제거합니다.
105
196
  *
197
+ * 삭제되는 항목에서 파일 읽기 정보를 추출하여 trimmedFileReads에 저장합니다.
198
+ * 이 정보는 나중에 system-reminder로 에이전트에게 알려줄 때 사용됩니다.
199
+ *
106
200
  * @param {Array} conversation - 대화 배열
107
201
  * @returns {boolean} 제거 성공 여부
108
202
  */
@@ -119,15 +213,53 @@ export function trimConversation(conversation) {
119
213
 
120
214
  debugLog(`[trimConversation] Target entry at index 1: type=${targetEntry?.type}, call_id=${targetCallId}`);
121
215
 
216
+ // 삭제 전에 파일 경로 추출
217
+ const filePaths = extractFilePathsFromEntry(targetEntry);
218
+ filePaths.forEach(path => {
219
+ // 최대 개수 초과 시 가장 오래된 항목 제거
220
+ if (trimmedFileReads.size >= MAX_TRIMMED_FILE_READS) {
221
+ const oldest = trimmedFileReads.values().next().value;
222
+ trimmedFileReads.delete(oldest);
223
+ debugLog(`[trimConversation] Removed oldest from trimmed file reads: ${oldest}`);
224
+ }
225
+ trimmedFileReads.add(path);
226
+ debugLog(`[trimConversation] Added to trimmed file reads: ${path}`);
227
+
228
+ // file_integrity에서 해시도 삭제 (에이전트가 파일 내용을 모르므로 편집 불가하게)
229
+ const hashCleared = clearFileContentHash(path);
230
+ debugLog(`[trimConversation] Cleared content hash for ${path}: ${hashCleared}`);
231
+ });
232
+
122
233
  // 먼저 인덱스 1 항목 제거
123
234
  conversation.splice(1, 1);
124
235
  debugLog(`[trimConversation] Removed entry at index 1`);
125
236
 
126
237
  // call_id가 있으면 같은 call_id를 가진 다른 항목들도 제거
127
238
  if (targetCallId) {
239
+ // 삭제 전에 관련 항목들에서 파일 경로 추출
240
+ for (const entry of conversation) {
241
+ if (entry && entry.call_id === targetCallId) {
242
+ const paths = extractFilePathsFromEntry(entry);
243
+ paths.forEach(path => {
244
+ // 최대 개수 초과 시 가장 오래된 항목 제거
245
+ if (trimmedFileReads.size >= MAX_TRIMMED_FILE_READS) {
246
+ const oldest = trimmedFileReads.values().next().value;
247
+ trimmedFileReads.delete(oldest);
248
+ debugLog(`[trimConversation] Removed oldest from trimmed file reads: ${oldest}`);
249
+ }
250
+ trimmedFileReads.add(path);
251
+ debugLog(`[trimConversation] Added related file to trimmed reads: ${path}`);
252
+
253
+ // file_integrity에서 해시도 삭제 (에이전트가 파일 내용을 모르므로 편집 불가하게)
254
+ const hashCleared = clearFileContentHash(path);
255
+ debugLog(`[trimConversation] Cleared content hash for ${path}: ${hashCleared}`);
256
+ });
257
+ }
258
+ }
128
259
  removeEntriesWithCallId(conversation, targetCallId);
129
260
  }
130
261
 
131
262
  debugLog(`[trimConversation] Trim completed. New conversation length: ${conversation.length}`);
263
+ debugLog(`[trimConversation] Total trimmed file reads: ${trimmedFileReads.size}`);
132
264
  return true;
133
265
  }