aiexecode 1.0.111 → 1.0.112
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/index.js +91 -41
- package/mcp-agent-lib/src/mcp_message_logger.js +17 -16
- package/package.json +1 -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/orchestrator.txt +62 -3
- package/src/ai_based/orchestrator.js +27 -2
- package/src/system/code_executer.js +30 -2
- package/src/system/conversation_trimmer.js +132 -0
- package/src/system/file_integrity.js +57 -0
- package/src/system/session.js +52 -8
- package/src/system/session_memory.js +30 -2
- package/src/system/system_info.js +254 -40
- package/src/tools/file_reader.js +17 -0
- package/src/util/prompt_loader.js +23 -0
- /package/payload_viewer/out/_next/static/{BqLAmXZiz76q8SE-Oia_y → kU_fT_YuMG0v1P4jppqF_}/_buildManifest.js +0 -0
- /package/payload_viewer/out/_next/static/{BqLAmXZiz76q8SE-Oia_y → kU_fT_YuMG0v1P4jppqF_}/_clientMiddlewareManifest.json +0 -0
- /package/payload_viewer/out/_next/static/{BqLAmXZiz76q8SE-Oia_y → kU_fT_YuMG0v1P4jppqF_}/_ssgManifest.js +0 -0
|
@@ -2,10 +2,10 @@ import dotenv from "dotenv";
|
|
|
2
2
|
// 이 파일은 계획을 실제 행동으로 옮기기 위해 어떤 도구를 호출할지 결정합니다.
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { truncateWithOmit } from "../util/text_formatter.js";
|
|
5
|
-
import { createSystemMessage, createTodoReminder, createSystemReminder } from "../util/prompt_loader.js";
|
|
5
|
+
import { createSystemMessage, createTodoReminder, createSystemReminder, createTrimmedFileReminder } from "../util/prompt_loader.js";
|
|
6
6
|
import { request, shouldRetryWithTrim, getModelForProvider } from "../system/ai_request.js";
|
|
7
7
|
import { supportsCaching } from "../config/ai_models.js";
|
|
8
|
-
import { cleanupOrphanOutputs, trimConversation } from "../system/conversation_trimmer.js";
|
|
8
|
+
import { cleanupOrphanOutputs, trimConversation, getTrimmedFileReads, clearTrimmedFileReads } from "../system/conversation_trimmer.js";
|
|
9
9
|
import { runPythonCodeSchema, bashSchema } from "../system/code_executer.js";
|
|
10
10
|
import { readFileSchema, readFileRangeSchema } from "../tools/file_reader.js";
|
|
11
11
|
import { writeFileSchema, editFileRangeSchema, editFileReplaceSchema } from "../tools/code_editor.js";
|
|
@@ -99,6 +99,20 @@ async function ensureConversationInitialized() {
|
|
|
99
99
|
debugLog(`[ensureConversationInitialized] Added ${currentTodos.length} todos as separate block (uncached)`);
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
// Trim된 파일 읽기 알림 추가 (파일 경로 알림 기능)
|
|
103
|
+
const trimmedFiles = getTrimmedFileReads();
|
|
104
|
+
if (trimmedFiles.length > 0) {
|
|
105
|
+
const trimmedFileReminder = createTrimmedFileReminder(trimmedFiles);
|
|
106
|
+
if (trimmedFileReminder) {
|
|
107
|
+
contentBlocks.push({
|
|
108
|
+
type: "input_text",
|
|
109
|
+
text: trimmedFileReminder
|
|
110
|
+
// cache_control 없음 - 동적 데이터이므로
|
|
111
|
+
});
|
|
112
|
+
debugLog(`[ensureConversationInitialized] Added trimmed file reminder for ${trimmedFiles.length} files (uncached)`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
102
116
|
systemMessageEntry = {
|
|
103
117
|
role: "system",
|
|
104
118
|
content: contentBlocks
|
|
@@ -133,6 +147,16 @@ async function ensureConversationInitialized() {
|
|
|
133
147
|
debugLog(`[ensureConversationInitialized] Added ${currentTodos.length} todos to system message`);
|
|
134
148
|
}
|
|
135
149
|
|
|
150
|
+
// Trim된 파일 읽기 알림 추가 (파일 경로 알림 기능)
|
|
151
|
+
const trimmedFiles = getTrimmedFileReads();
|
|
152
|
+
if (trimmedFiles.length > 0) {
|
|
153
|
+
const trimmedFileReminder = createTrimmedFileReminder(trimmedFiles);
|
|
154
|
+
if (trimmedFileReminder) {
|
|
155
|
+
systemMessageText += '\n\n' + trimmedFileReminder;
|
|
156
|
+
debugLog(`[ensureConversationInitialized] Added trimmed file reminder for ${trimmedFiles.length} files`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
136
160
|
systemMessageEntry = {
|
|
137
161
|
role: "system",
|
|
138
162
|
content: [
|
|
@@ -178,6 +202,7 @@ function appendResponseToConversation(response) {
|
|
|
178
202
|
export function resetOrchestratorConversation() {
|
|
179
203
|
orchestratorConversation.length = 0;
|
|
180
204
|
orchestratorRequestOptions = null;
|
|
205
|
+
clearTrimmedFileReads(); // 세션 초기화 시 trim된 파일 목록도 초기화
|
|
181
206
|
}
|
|
182
207
|
|
|
183
208
|
export function getOrchestratorConversation() {
|
|
@@ -581,14 +581,42 @@ export const runPythonCodeSchema = {
|
|
|
581
581
|
|
|
582
582
|
export const bashSchema = {
|
|
583
583
|
name: "bash",
|
|
584
|
-
description:
|
|
584
|
+
description: `Executes bash shell commands with optional timeout.
|
|
585
|
+
|
|
586
|
+
IMPORTANT: This tool is for terminal operations like git, npm, docker, etc. DO NOT use it for file reading/searching operations - use specialized tools instead.
|
|
587
|
+
|
|
588
|
+
Output Limits:
|
|
589
|
+
- stdout is truncated to 8000 characters
|
|
590
|
+
- stderr is truncated to 4000 characters
|
|
591
|
+
- Commands timing out after 20 minutes will be terminated
|
|
592
|
+
- If output exceeds limits, only the first 70% and last 30% are shown with an omission notice
|
|
593
|
+
|
|
594
|
+
Avoid These Commands (Use Specialized Tools Instead):
|
|
595
|
+
- File reading: Use read_file or read_file_range instead of cat, head, tail
|
|
596
|
+
- Content search: Use ripgrep instead of grep or rg
|
|
597
|
+
- File search: Use glob_search instead of find
|
|
598
|
+
- File editing: Use edit_file_replace or write_file instead of sed, awk, echo >
|
|
599
|
+
|
|
600
|
+
When Output May Be Large:
|
|
601
|
+
- Limit output with: command | head -50
|
|
602
|
+
- Save to file: command > output.log 2>&1
|
|
603
|
+
- Filter results: command | grep "pattern"
|
|
604
|
+
- Use quiet/minimal flags when available
|
|
605
|
+
|
|
606
|
+
Best Practices:
|
|
607
|
+
- Use auto-confirm flags: -y, -f, --yes, --quiet
|
|
608
|
+
- Chain commands with && for sequential execution
|
|
609
|
+
- Avoid interactive commands that require user input
|
|
610
|
+
- For calculations, use non-interactive bc or Python (never mental arithmetic)
|
|
611
|
+
|
|
612
|
+
This tool handles: file system operations (mkdir, rm, mv, cp, chmod), git commands, package management (npm, pip), and CLI tools.`,
|
|
585
613
|
parameters: {
|
|
586
614
|
type: "object",
|
|
587
615
|
required: ["script"],
|
|
588
616
|
properties: {
|
|
589
617
|
script: {
|
|
590
618
|
type: "string",
|
|
591
|
-
description: "Bash script to execute. Will be saved to a temporary file and run with bash.
|
|
619
|
+
description: "Bash script to execute. Will be saved to a temporary file and run with bash. Keep scripts concise and output-limited to avoid truncation."
|
|
592
620
|
}
|
|
593
621
|
},
|
|
594
622
|
additionalProperties: false
|
|
@@ -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
|
}
|
|
@@ -112,6 +112,38 @@ class FileIntegrityTracker {
|
|
|
112
112
|
return sessionFiles.get(filePath) || null;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
/**
|
|
116
|
+
* 파일 콘텐츠 해시를 삭제합니다
|
|
117
|
+
* 대화가 trim되어 에이전트가 파일 내용을 알 수 없게 되었을 때 호출됩니다.
|
|
118
|
+
* 이렇게 하면 에이전트가 파일을 다시 읽기 전까지 편집할 수 없습니다.
|
|
119
|
+
* @param {string} sessionID - 세션 ID
|
|
120
|
+
* @param {string} filePath - 파일 경로
|
|
121
|
+
* @returns {boolean} 삭제 성공 여부
|
|
122
|
+
*/
|
|
123
|
+
clearContentHash(sessionID, filePath) {
|
|
124
|
+
debugLog(`========== clearContentHash START ==========`);
|
|
125
|
+
debugLog(`sessionID: ${sessionID}`);
|
|
126
|
+
debugLog(`filePath: ${filePath}`);
|
|
127
|
+
|
|
128
|
+
const sessionFiles = this.contentHashes.get(sessionID);
|
|
129
|
+
if (!sessionFiles) {
|
|
130
|
+
debugLog(`No session map found for session: ${sessionID}`);
|
|
131
|
+
debugLog(`========== clearContentHash END (NO SESSION) ==========`);
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const existed = sessionFiles.has(filePath);
|
|
136
|
+
if (existed) {
|
|
137
|
+
sessionFiles.delete(filePath);
|
|
138
|
+
debugLog(`Hash deleted for file: ${filePath}`);
|
|
139
|
+
} else {
|
|
140
|
+
debugLog(`No hash found for file: ${filePath}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
debugLog(`========== clearContentHash END ==========`);
|
|
144
|
+
return existed;
|
|
145
|
+
}
|
|
146
|
+
|
|
115
147
|
/**
|
|
116
148
|
* 파일 편집 전 무결성을 검증합니다
|
|
117
149
|
* @param {string} sessionID - 세션 ID
|
|
@@ -347,6 +379,31 @@ export async function assertFileIntegrity(filePath) {
|
|
|
347
379
|
debugLog('========== assertFileIntegrity (export wrapper) END ==========');
|
|
348
380
|
}
|
|
349
381
|
|
|
382
|
+
/**
|
|
383
|
+
* 파일 콘텐츠 해시를 삭제합니다 (현재 세션 기준)
|
|
384
|
+
* 대화가 trim되어 에이전트가 파일 내용을 알 수 없게 되었을 때 호출됩니다.
|
|
385
|
+
* @param {string} filePath - 파일 경로
|
|
386
|
+
* @returns {boolean} 삭제 성공 여부
|
|
387
|
+
*/
|
|
388
|
+
export function clearFileContentHash(filePath) {
|
|
389
|
+
debugLog(`========== clearFileContentHash (export wrapper) START ==========`);
|
|
390
|
+
debugLog(`filePath: ${filePath}`);
|
|
391
|
+
|
|
392
|
+
const sessionID = fileIntegrityTracker.getCurrentSession();
|
|
393
|
+
debugLog(`Current session ID: ${sessionID || 'NULL'}`);
|
|
394
|
+
|
|
395
|
+
if (!sessionID) {
|
|
396
|
+
debugLog(`ERROR: No current session set, cannot clear hash`);
|
|
397
|
+
debugLog('========== clearFileContentHash (export wrapper) END ==========');
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const result = fileIntegrityTracker.clearContentHash(sessionID, filePath);
|
|
402
|
+
debugLog(`Hash clear result: ${result}`);
|
|
403
|
+
debugLog('========== clearFileContentHash (export wrapper) END ==========');
|
|
404
|
+
return result;
|
|
405
|
+
}
|
|
406
|
+
|
|
350
407
|
/**
|
|
351
408
|
* 파일 스냅샷을 저장합니다 (현재 세션 기준)
|
|
352
409
|
* @param {string} filePath - 파일 경로
|
package/src/system/session.js
CHANGED
|
@@ -12,7 +12,7 @@ import { TODO_WRITE_FUNCTIONS } from '../tools/todo_write.js';
|
|
|
12
12
|
import { SKILL_FUNCTIONS } from '../tools/skill_tool.js';
|
|
13
13
|
import { clampOutput, formatToolStdout } from "../util/output_formatter.js";
|
|
14
14
|
import { buildToolHistoryEntry } from "../util/rag_helper.js";
|
|
15
|
-
import { createSessionData, getLastConversationState, loadPreviousSessions, saveSessionToHistory, saveTodosToSession, restoreTodosFromSession, updateCurrentTodos, getCurrentTodos } from "./session_memory.js";
|
|
15
|
+
import { createSessionData, getLastConversationState, loadPreviousSessions, saveSessionToHistory, saveTodosToSession, restoreTodosFromSession, saveTrimmedFileReadsToSession, restoreTrimmedFileReadsFromSession, updateCurrentTodos, getCurrentTodos } from "./session_memory.js";
|
|
16
16
|
import { uiEvents } from "./ui_events.js";
|
|
17
17
|
import { logSystem, logError, logAssistantMessage, logToolCall, logToolResult, logCodeExecution, logCodeResult, logIteration, logMissionComplete, logConversationRestored } from "./output_helper.js";
|
|
18
18
|
import { requiresApproval, requestApproval } from "./tool_approval.js";
|
|
@@ -887,6 +887,11 @@ export async function runSession(options) {
|
|
|
887
887
|
restoreTodosFromSession({ currentTodos: conversationState.currentTodos });
|
|
888
888
|
debugLog(`[runSession] Restored ${conversationState.currentTodos.length} todos from previous session`);
|
|
889
889
|
}
|
|
890
|
+
// TrimmedFileReads 복원
|
|
891
|
+
if (conversationState.trimmedFileReads) {
|
|
892
|
+
restoreTrimmedFileReadsFromSession({ trimmedFileReads: conversationState.trimmedFileReads });
|
|
893
|
+
debugLog(`[runSession] Restored ${conversationState.trimmedFileReads.length} trimmed file reads from previous session`);
|
|
894
|
+
}
|
|
890
895
|
// logSuccess('✓ Conversation state restored');
|
|
891
896
|
} else {
|
|
892
897
|
// logSystem('ℹ Starting with fresh conversation state');
|
|
@@ -1054,6 +1059,9 @@ export async function runSession(options) {
|
|
|
1054
1059
|
// Todos를 세션에 저장
|
|
1055
1060
|
saveTodosToSession(currentSessionData);
|
|
1056
1061
|
|
|
1062
|
+
// TrimmedFileReads를 세션에 저장
|
|
1063
|
+
saveTrimmedFileReadsToSession(currentSessionData);
|
|
1064
|
+
|
|
1057
1065
|
debugLog(`[ITERATION ${iteration_count}] Saving session to history after completion_judge (mission_solved=${mission_solved})`);
|
|
1058
1066
|
await saveSessionToHistory(currentSessionData).catch(err => {
|
|
1059
1067
|
debugLog(`[ITERATION ${iteration_count}] Failed to save session after completion_judge: ${err.message}`);
|
|
@@ -1082,10 +1090,14 @@ export async function runSession(options) {
|
|
|
1082
1090
|
currentSessionData.orchestratorConversation = getOrchestratorConversation();
|
|
1083
1091
|
currentSessionData.orchestratorRequestOptions = null;
|
|
1084
1092
|
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1093
|
+
// 인터럽트 시에도 Todos와 TrimmedFileReads 저장 (다음 세션에서 복원 가능하도록)
|
|
1094
|
+
saveTodosToSession(currentSessionData);
|
|
1095
|
+
saveTrimmedFileReadsToSession(currentSessionData);
|
|
1096
|
+
|
|
1097
|
+
debugLog(`[ITERATION ${iteration_count}] Session interrupted - saving trimmedFileReads and todos`);
|
|
1098
|
+
await saveSessionToHistory(currentSessionData).catch(err => {
|
|
1099
|
+
debugLog(`[ITERATION ${iteration_count}] Failed to save session on interrupt: ${err.message}`);
|
|
1100
|
+
});
|
|
1089
1101
|
break;
|
|
1090
1102
|
}
|
|
1091
1103
|
|
|
@@ -1119,6 +1131,21 @@ export async function runSession(options) {
|
|
|
1119
1131
|
: '✅ No function calls detected - mission complete';
|
|
1120
1132
|
uiEvents.addSystemMessage(message);
|
|
1121
1133
|
debugLog(`[ITERATION ${iteration_count}] ${message}`);
|
|
1134
|
+
|
|
1135
|
+
// completion_judge 없이 종료되는 경우에도 Todos와 TrimmedFileReads 저장
|
|
1136
|
+
currentSessionData.completed_at = new Date().toISOString();
|
|
1137
|
+
currentSessionData.mission_solved = true;
|
|
1138
|
+
currentSessionData.iteration_count = iteration_count;
|
|
1139
|
+
currentSessionData.toolUsageHistory = toolUsageHistory;
|
|
1140
|
+
currentSessionData.orchestratorConversation = getOrchestratorConversation();
|
|
1141
|
+
currentSessionData.orchestratorRequestOptions = null;
|
|
1142
|
+
saveTodosToSession(currentSessionData);
|
|
1143
|
+
saveTrimmedFileReadsToSession(currentSessionData);
|
|
1144
|
+
debugLog(`[ITERATION ${iteration_count}] Saving session on no-function-call exit`);
|
|
1145
|
+
await saveSessionToHistory(currentSessionData).catch(err => {
|
|
1146
|
+
debugLog(`[ITERATION ${iteration_count}] Failed to save session: ${err.message}`);
|
|
1147
|
+
});
|
|
1148
|
+
|
|
1122
1149
|
debugLog('========================================');
|
|
1123
1150
|
debugLog(`========== ITERATION ${iteration_count} END ==========`);
|
|
1124
1151
|
debugLog('========================================');
|
|
@@ -1158,12 +1185,17 @@ export async function runSession(options) {
|
|
|
1158
1185
|
currentSessionData.orchestratorConversation = result.orchestratorConversation;
|
|
1159
1186
|
currentSessionData.orchestratorRequestOptions = null; // 필요시 orchestrator에서 가져올 수 있음
|
|
1160
1187
|
|
|
1161
|
-
|
|
1188
|
+
// 루프 정상 종료 시에도 Todos와 TrimmedFileReads 저장
|
|
1189
|
+
saveTodosToSession(currentSessionData);
|
|
1190
|
+
saveTrimmedFileReadsToSession(currentSessionData);
|
|
1191
|
+
|
|
1192
|
+
debugLog(`[runSession] Saving final session to history - sessionID: ${currentSessionData.sessionID}`);
|
|
1162
1193
|
debugLog(`[runSession] Session data size: ${JSON.stringify(currentSessionData).length} bytes`);
|
|
1163
1194
|
debugLog(`[runSession] Final state - mission_solved: ${currentSessionData.mission_solved}, iteration_count: ${currentSessionData.iteration_count}`);
|
|
1164
1195
|
debugLog(`[runSession] Tool usage history entries: ${currentSessionData.toolUsageHistory.length}`);
|
|
1165
|
-
|
|
1166
|
-
|
|
1196
|
+
await saveSessionToHistory(currentSessionData).catch(err => {
|
|
1197
|
+
debugLog(`[runSession] Failed to save final session: ${err.message}`);
|
|
1198
|
+
});
|
|
1167
1199
|
|
|
1168
1200
|
debugLog('========================================');
|
|
1169
1201
|
debugLog('========== runSession END ==========');
|
|
@@ -1198,6 +1230,18 @@ export async function runSession(options) {
|
|
|
1198
1230
|
|
|
1199
1231
|
uiEvents.addErrorMessage(consolidatedErrorMessage);
|
|
1200
1232
|
|
|
1233
|
+
// 에러 발생 시에도 Todos와 TrimmedFileReads 저장 시도
|
|
1234
|
+
if (currentSessionData) {
|
|
1235
|
+
currentSessionData.completed_at = new Date().toISOString();
|
|
1236
|
+
currentSessionData.mission_solved = false;
|
|
1237
|
+
saveTodosToSession(currentSessionData);
|
|
1238
|
+
saveTrimmedFileReadsToSession(currentSessionData);
|
|
1239
|
+
debugLog(`[runSession] Saving session on error`);
|
|
1240
|
+
await saveSessionToHistory(currentSessionData).catch(err => {
|
|
1241
|
+
debugLog(`[runSession] Failed to save session on error: ${err.message}`);
|
|
1242
|
+
});
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1201
1245
|
// 에러를 throw하지 않고 정상적으로 종료
|
|
1202
1246
|
return null;
|
|
1203
1247
|
} finally {
|
|
@@ -4,6 +4,7 @@ import { join, resolve } from 'path';
|
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
import { formatToolCall, formatToolResult, getToolDisplayName, getToolDisplayConfig } from './tool_registry.js';
|
|
6
6
|
import { createDebugLogger } from '../util/debug_log.js';
|
|
7
|
+
import { getTrimmedFileReads, setTrimmedFileReads } from './conversation_trimmer.js';
|
|
7
8
|
|
|
8
9
|
const MAX_HISTORY_SESSIONS = 1; // 최대 보관 세션 수
|
|
9
10
|
|
|
@@ -11,7 +12,7 @@ const debugLog = createDebugLogger('session_memory.log', 'session_memory');
|
|
|
11
12
|
|
|
12
13
|
// 현재 작업 디렉토리 기준 세션 디렉토리 경로 생성
|
|
13
14
|
function getSessionDir(sessionID) {
|
|
14
|
-
return join(process.cwd(), '.aiexe', sessionID);
|
|
15
|
+
return join(process.cwd(), '.aiexe', 'sessions', sessionID);
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
// 세션 히스토리 파일 경로 생성
|
|
@@ -150,7 +151,8 @@ export function getLastConversationState(sessions) {
|
|
|
150
151
|
return {
|
|
151
152
|
orchestratorConversation: lastSession.orchestratorConversation || [],
|
|
152
153
|
orchestratorRequestOptions: lastSession.orchestratorRequestOptions || null,
|
|
153
|
-
currentTodos: lastSession.currentTodos || []
|
|
154
|
+
currentTodos: lastSession.currentTodos || [],
|
|
155
|
+
trimmedFileReads: lastSession.trimmedFileReads || []
|
|
154
156
|
};
|
|
155
157
|
}
|
|
156
158
|
|
|
@@ -450,3 +452,29 @@ export function restoreTodosFromSession(sessionData) {
|
|
|
450
452
|
currentSessionTodos = [];
|
|
451
453
|
}
|
|
452
454
|
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* 세션 데이터에 trimmedFileReads를 저장
|
|
458
|
+
* @param {Object} sessionData - 세션 데이터 객체
|
|
459
|
+
*/
|
|
460
|
+
export function saveTrimmedFileReadsToSession(sessionData) {
|
|
461
|
+
debugLog('========== saveTrimmedFileReadsToSession ==========');
|
|
462
|
+
const trimmedFiles = getTrimmedFileReads();
|
|
463
|
+
debugLog(`Saving ${trimmedFiles.length} trimmed file reads to session`);
|
|
464
|
+
sessionData.trimmedFileReads = trimmedFiles;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* 세션 데이터에서 trimmedFileReads를 복원
|
|
469
|
+
* @param {Object} sessionData - 세션 데이터 객체
|
|
470
|
+
*/
|
|
471
|
+
export function restoreTrimmedFileReadsFromSession(sessionData) {
|
|
472
|
+
debugLog('========== restoreTrimmedFileReadsFromSession ==========');
|
|
473
|
+
if (sessionData && Array.isArray(sessionData.trimmedFileReads)) {
|
|
474
|
+
debugLog(`Restoring ${sessionData.trimmedFileReads.length} trimmed file reads from session`);
|
|
475
|
+
setTrimmedFileReads(sessionData.trimmedFileReads);
|
|
476
|
+
} else {
|
|
477
|
+
debugLog('No trimmed file reads to restore');
|
|
478
|
+
setTrimmedFileReads([]);
|
|
479
|
+
}
|
|
480
|
+
}
|