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.
- package/README.md +198 -88
- package/index.js +310 -86
- package/mcp-agent-lib/src/mcp_message_logger.js +17 -16
- package/package.json +4 -4
- package/payload_viewer/out/404/index.html +1 -1
- package/payload_viewer/out/404.html +1 -1
- package/payload_viewer/out/_next/static/chunks/{37d0cd2587a38f79.js → b6c0459f3789d25c.js} +1 -1
- package/payload_viewer/out/_next/static/chunks/b75131b58f8ca46a.css +3 -0
- package/payload_viewer/out/index.html +1 -1
- package/payload_viewer/out/index.txt +3 -3
- package/payload_viewer/web_server.js +361 -0
- package/prompts/completion_judge.txt +4 -0
- package/prompts/orchestrator.txt +116 -3
- package/src/LLMClient/client.js +401 -18
- package/src/LLMClient/converters/responses-to-claude.js +67 -18
- package/src/LLMClient/converters/responses-to-zai.js +667 -0
- package/src/LLMClient/errors.js +30 -4
- package/src/LLMClient/index.js +5 -0
- package/src/ai_based/completion_judge.js +263 -186
- package/src/ai_based/orchestrator.js +171 -35
- package/src/commands/agents.js +70 -0
- package/src/commands/apikey.js +1 -1
- package/src/commands/bg.js +129 -0
- package/src/commands/commands.js +51 -0
- package/src/commands/debug.js +52 -0
- package/src/commands/help.js +11 -1
- package/src/commands/model.js +42 -7
- package/src/commands/reasoning_effort.js +2 -2
- package/src/commands/skills.js +46 -0
- package/src/config/ai_models.js +106 -6
- package/src/config/constants.js +71 -0
- package/src/config/feature_flags.js +6 -7
- package/src/frontend/App.js +108 -1
- package/src/frontend/components/AutocompleteMenu.js +7 -1
- package/src/frontend/components/BackgroundProcessList.js +175 -0
- package/src/frontend/components/ConversationItem.js +26 -10
- package/src/frontend/components/CurrentModelView.js +2 -2
- package/src/frontend/components/HelpView.js +106 -2
- package/src/frontend/components/Input.js +33 -11
- package/src/frontend/components/ModelListView.js +1 -1
- package/src/frontend/components/SetupWizard.js +51 -8
- package/src/frontend/hooks/useFileCompletion.js +467 -0
- package/src/frontend/utils/toolUIFormatter.js +261 -0
- package/src/system/agents_loader.js +289 -0
- package/src/system/ai_request.js +156 -12
- package/src/system/background_process.js +317 -0
- package/src/system/code_executer.js +496 -56
- package/src/system/command_parser.js +33 -3
- package/src/system/conversation_state.js +265 -0
- package/src/system/conversation_trimmer.js +132 -0
- package/src/system/custom_command_loader.js +386 -0
- package/src/system/file_integrity.js +73 -10
- package/src/system/log.js +10 -2
- package/src/system/output_helper.js +52 -9
- package/src/system/session.js +213 -58
- package/src/system/session_memory.js +30 -2
- package/src/system/skill_loader.js +318 -0
- package/src/system/system_info.js +254 -40
- package/src/system/tool_approval.js +10 -0
- package/src/system/tool_registry.js +15 -1
- package/src/system/ui_events.js +11 -0
- package/src/tools/code_editor.js +16 -10
- package/src/tools/file_reader.js +66 -9
- package/src/tools/glob.js +0 -3
- package/src/tools/ripgrep.js +5 -7
- package/src/tools/skill_tool.js +122 -0
- package/src/tools/web_downloader.js +0 -3
- package/src/util/clone.js +174 -0
- package/src/util/config.js +55 -2
- package/src/util/config_migration.js +174 -0
- package/src/util/debug_log.js +8 -2
- package/src/util/exit_handler.js +8 -0
- package/src/util/file_reference_parser.js +132 -0
- package/src/util/path_validator.js +178 -0
- package/src/util/prompt_loader.js +91 -1
- package/src/util/safe_fs.js +66 -3
- package/payload_viewer/out/_next/static/chunks/ecd2072ebf41611f.css +0 -3
- /package/payload_viewer/out/_next/static/{wkEKh6i9XPSyP6rjDRvHn → 42iEoi-1o5MxNIZ1SWSvV}/_buildManifest.js +0 -0
- /package/payload_viewer/out/_next/static/{wkEKh6i9XPSyP6rjDRvHn → 42iEoi-1o5MxNIZ1SWSvV}/_clientMiddlewareManifest.json +0 -0
- /package/payload_viewer/out/_next/static/{wkEKh6i9XPSyP6rjDRvHn → 42iEoi-1o5MxNIZ1SWSvV}/_ssgManifest.js +0 -0
package/src/system/session.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// Agent Loop: 계획·실행·검증 사이클을 수행하는 핵심 모듈
|
|
2
2
|
import { orchestrateMission, continueOrchestratorConversation, resetOrchestratorConversation, recordOrchestratorToolResult, getOrchestratorConversation, restoreOrchestratorConversation } from "../ai_based/orchestrator.js";
|
|
3
3
|
import { judgeMissionCompletion } from "../ai_based/completion_judge.js";
|
|
4
|
-
import { execPythonCode, execShellScript } from "./code_executer.js";
|
|
4
|
+
import { execPythonCode, execShellScript, resetShellCwd } from "./code_executer.js";
|
|
5
|
+
import { runInBackground, getBackgroundProcess } from "./background_process.js";
|
|
5
6
|
import { FILE_READER_FUNCTIONS } from "../tools/file_reader.js";
|
|
6
7
|
import { RIPGREP_FUNCTIONS } from "../tools/ripgrep.js";
|
|
7
8
|
import { GLOB_FUNCTIONS } from "../tools/glob.js";
|
|
@@ -9,9 +10,10 @@ import { CODE_EDITOR_FUNCTIONS } from "../tools/code_editor.js";
|
|
|
9
10
|
import { WEB_DOWNLOADER_FUNCTIONS } from "../tools/web_downloader.js";
|
|
10
11
|
import { RESPONSE_MESSAGE_FUNCTIONS } from '../tools/response_message.js';
|
|
11
12
|
import { TODO_WRITE_FUNCTIONS } from '../tools/todo_write.js';
|
|
13
|
+
import { SKILL_FUNCTIONS } from '../tools/skill_tool.js';
|
|
12
14
|
import { clampOutput, formatToolStdout } from "../util/output_formatter.js";
|
|
13
15
|
import { buildToolHistoryEntry } from "../util/rag_helper.js";
|
|
14
|
-
import { createSessionData, getLastConversationState, loadPreviousSessions, saveSessionToHistory, saveTodosToSession, restoreTodosFromSession, updateCurrentTodos, getCurrentTodos } from "./session_memory.js";
|
|
16
|
+
import { createSessionData, getLastConversationState, loadPreviousSessions, saveSessionToHistory, saveTodosToSession, restoreTodosFromSession, saveTrimmedFileReadsToSession, restoreTrimmedFileReadsFromSession, updateCurrentTodos, getCurrentTodos } from "./session_memory.js";
|
|
15
17
|
import { uiEvents } from "./ui_events.js";
|
|
16
18
|
import { logSystem, logError, logAssistantMessage, logToolCall, logToolResult, logCodeExecution, logCodeResult, logIteration, logMissionComplete, logConversationRestored } from "./output_helper.js";
|
|
17
19
|
import { requiresApproval, requestApproval } from "./tool_approval.js";
|
|
@@ -20,16 +22,55 @@ import { abortCurrentRequest } from "./ai_request.js";
|
|
|
20
22
|
import { safeReadFile } from '../util/safe_fs.js';
|
|
21
23
|
import { resolve } from 'path';
|
|
22
24
|
import { createDebugLogger } from '../util/debug_log.js';
|
|
23
|
-
import {
|
|
25
|
+
import { loadSettings } from '../util/config.js';
|
|
26
|
+
import {
|
|
27
|
+
MAX_REASONING_ONLY_RESPONSES,
|
|
28
|
+
DEFAULT_MAX_ITERATIONS,
|
|
29
|
+
SUB_MISSION_MAX_LENGTH
|
|
30
|
+
} from '../config/constants.js';
|
|
24
31
|
|
|
25
32
|
const debugLog = createDebugLogger('session.log', 'session');
|
|
26
33
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
34
|
+
// Pipe mode helper: UI 이벤트를 조건부로 처리
|
|
35
|
+
const isPipeMode = () => process.app_custom?.pipeMode === true;
|
|
36
|
+
|
|
37
|
+
// Pipe mode에서는 stderr로 출력, 일반 모드에서는 uiEvents 사용
|
|
38
|
+
const safeUiEvents = {
|
|
39
|
+
addSystemMessage: (msg) => {
|
|
40
|
+
if (isPipeMode()) {
|
|
41
|
+
console.error(`[SYSTEM] ${msg}`);
|
|
42
|
+
} else {
|
|
43
|
+
uiEvents.addSystemMessage(msg);
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
addErrorMessage: (msg) => {
|
|
47
|
+
if (isPipeMode()) {
|
|
48
|
+
console.error(`[ERROR] ${msg}`);
|
|
49
|
+
} else {
|
|
50
|
+
uiEvents.addErrorMessage(msg);
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
sessionStart: (msg) => {
|
|
54
|
+
if (!isPipeMode()) {
|
|
55
|
+
uiEvents.sessionStart(msg);
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
sessionEnd: () => {
|
|
59
|
+
if (!isPipeMode()) {
|
|
60
|
+
uiEvents.sessionEnd();
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
on: (event, handler) => {
|
|
64
|
+
if (!isPipeMode()) {
|
|
65
|
+
uiEvents.on(event, handler);
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
off: (event, handler) => {
|
|
69
|
+
if (!isPipeMode()) {
|
|
70
|
+
uiEvents.off(event, handler);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
};
|
|
33
74
|
|
|
34
75
|
|
|
35
76
|
/**
|
|
@@ -391,11 +432,14 @@ async function processOrchestratorResponses(params) {
|
|
|
391
432
|
);
|
|
392
433
|
debugLog(`Has processable output: ${hasProcessableOutput}`);
|
|
393
434
|
|
|
435
|
+
// pendingMessageOutput은 while 루프 스코프에서 유지 (function call 없이 끝날 때 출력용)
|
|
436
|
+
let pendingMessageOutput = null;
|
|
437
|
+
|
|
394
438
|
if (!hasProcessableOutput) {
|
|
395
439
|
debugLog(`No processable output, reasoningOnlyResponses: ${reasoningOnlyResponses}`);
|
|
396
440
|
if (reasoningOnlyResponses >= MAX_REASONING_ONLY_RESPONSES) {
|
|
397
441
|
debugLog(`Reached MAX_REASONING_ONLY_RESPONSES (${MAX_REASONING_ONLY_RESPONSES}), treating as stall`);
|
|
398
|
-
|
|
442
|
+
safeUiEvents.addSystemMessage('⚠ Orchestrator stalled (reasoning only) - treating as completion');
|
|
399
443
|
stallDetected = true;
|
|
400
444
|
break;
|
|
401
445
|
}
|
|
@@ -419,7 +463,6 @@ async function processOrchestratorResponses(params) {
|
|
|
419
463
|
reasoningOnlyResponses = 0;
|
|
420
464
|
let executedFunctionCall = false;
|
|
421
465
|
let lastOutputType = null;
|
|
422
|
-
let pendingMessageOutput = null; // 출력 대기 중인 MESSAGE 저장
|
|
423
466
|
|
|
424
467
|
// 각 출력 처리
|
|
425
468
|
for (let outputIndex = 0; outputIndex < outputs.length; outputIndex++) {
|
|
@@ -598,8 +641,33 @@ async function processOrchestratorResponses(params) {
|
|
|
598
641
|
|
|
599
642
|
// 완료 조건 체크
|
|
600
643
|
debugLog(`Checking completion conditions...`);
|
|
601
|
-
|
|
602
|
-
|
|
644
|
+
debugLog(` executedFunctionCall: ${executedFunctionCall}, hadAnyFunctionCall: ${hadAnyFunctionCall}, lastOutputType: ${lastOutputType}`);
|
|
645
|
+
|
|
646
|
+
// Case 1: Function call 없이 message만 있는 경우 (단순 대화, 인사 등)
|
|
647
|
+
// → Completion Judge 없이 바로 message 출력하고 완료
|
|
648
|
+
if (!executedFunctionCall && lastOutputType === 'message' && !hadAnyFunctionCall) {
|
|
649
|
+
debugLog(`COMPLETION: No function call at all + message type - completing immediately without Completion Judge`);
|
|
650
|
+
|
|
651
|
+
// 대기 중인 메시지 출력
|
|
652
|
+
if (pendingMessageOutput) {
|
|
653
|
+
debugLog(`Displaying pending message immediately`);
|
|
654
|
+
processMessageOutput(pendingMessageOutput, true);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
missionSolved = true;
|
|
658
|
+
break;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// Case 2: Function call 실행 후 message로 결과 보고
|
|
662
|
+
// → message 먼저 출력 → Completion Judge로 추가 작업 판단
|
|
663
|
+
if (!executedFunctionCall && lastOutputType === 'message' && hadAnyFunctionCall) {
|
|
664
|
+
debugLog(`COMPLETION: Had function calls + final message - displaying message first, then judging completion`);
|
|
665
|
+
|
|
666
|
+
// 먼저 message 출력 (사용자가 바로 결과 확인 가능)
|
|
667
|
+
if (pendingMessageOutput) {
|
|
668
|
+
debugLog(`Displaying pending message before completion judge`);
|
|
669
|
+
processMessageOutput(pendingMessageOutput, true);
|
|
670
|
+
}
|
|
603
671
|
|
|
604
672
|
// 세션 중단 확인 (completion_judge 호출 전)
|
|
605
673
|
if (sessionInterrupted) {
|
|
@@ -646,45 +714,30 @@ async function processOrchestratorResponses(params) {
|
|
|
646
714
|
missionSolved = judgement.shouldComplete;
|
|
647
715
|
|
|
648
716
|
if (missionSolved) {
|
|
649
|
-
// 미션이 완료되었다고
|
|
650
|
-
debugLog(`Mission judged as complete,
|
|
651
|
-
if (pendingMessageOutput) {
|
|
652
|
-
processMessageOutput(pendingMessageOutput, true); // 이제 출력
|
|
653
|
-
}
|
|
717
|
+
// 미션이 완료되었다고 판단 (message는 이미 출력됨)
|
|
718
|
+
debugLog(`Mission judged as complete, breaking loop`);
|
|
654
719
|
break;
|
|
655
720
|
} else {
|
|
656
|
-
// 미션이 완료되지 않았다고 판단되면
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
// orchestratorConversation에서 방금 추가된 assistant message에 _internal_only 플래그 추가
|
|
660
|
-
const conversation = getOrchestratorConversation();
|
|
661
|
-
for (let i = conversation.length - 1; i >= 0; i--) {
|
|
662
|
-
const entry = conversation[i];
|
|
663
|
-
if (entry.type === 'message' && entry.role === 'assistant') {
|
|
664
|
-
entry._internal_only = true;
|
|
665
|
-
debugLog(`[_internal_only] Marked assistant message at index ${i} as internal-only (not for display)`);
|
|
666
|
-
break;
|
|
667
|
-
}
|
|
668
|
-
}
|
|
721
|
+
// 미션이 완료되지 않았다고 판단되면 계속 진행
|
|
722
|
+
// (message는 이미 출력됨 - 사용자가 진행 상황 확인 가능)
|
|
723
|
+
debugLog(`Mission not complete, continuing...`);
|
|
669
724
|
|
|
670
725
|
// whatUserShouldSay를 새로운 사용자 요구사항으로 처리
|
|
671
726
|
if (judgement.whatUserShouldSay && judgement.whatUserShouldSay.trim().length > 0) {
|
|
672
727
|
debugLog(`Treating whatUserShouldSay as new user request: ${judgement.whatUserShouldSay}`);
|
|
673
|
-
// 💬 Auto-continuing 메시지 제거
|
|
674
728
|
|
|
675
729
|
// whatUserShouldSay를 새로운 mission으로 설정하여 루프를 빠져나감
|
|
676
|
-
// 상위 runSession 루프에서 다시 orchestrateMission이 호출될 것임
|
|
677
730
|
improvementPoints = judgement.whatUserShouldSay;
|
|
678
|
-
improvementPointsIsAutoGenerated = true;
|
|
731
|
+
improvementPointsIsAutoGenerated = true;
|
|
679
732
|
debugLog(`[_internal_only] Set improvementPointsIsAutoGenerated=true for whatUserShouldSay`);
|
|
680
|
-
missionSolved = false;
|
|
681
|
-
break;
|
|
733
|
+
missionSolved = false;
|
|
734
|
+
break;
|
|
682
735
|
} else {
|
|
683
736
|
// whatUserShouldSay가 없으면 빈 문자열로 계속 진행
|
|
684
737
|
improvementPoints = "";
|
|
685
738
|
debugLog(`Continuing mission without whatUserShouldSay`);
|
|
686
739
|
|
|
687
|
-
// 세션 중단 확인
|
|
740
|
+
// 세션 중단 확인
|
|
688
741
|
if (sessionInterrupted) {
|
|
689
742
|
debugLog(`Session interrupted before continueOrchestratorConversation, breaking loop`);
|
|
690
743
|
break;
|
|
@@ -693,7 +746,7 @@ async function processOrchestratorResponses(params) {
|
|
|
693
746
|
try {
|
|
694
747
|
orchestratedResponse = await continueOrchestratorConversation();
|
|
695
748
|
debugLog(`Received response with ${orchestratedResponse?.output?.length || 0} outputs`);
|
|
696
|
-
continue;
|
|
749
|
+
continue;
|
|
697
750
|
} catch (err) {
|
|
698
751
|
if (err.name === 'AbortError' || sessionInterrupted) {
|
|
699
752
|
debugLog(`Conversation aborted or interrupted: ${err.name}`);
|
|
@@ -706,11 +759,23 @@ async function processOrchestratorResponses(params) {
|
|
|
706
759
|
}
|
|
707
760
|
}
|
|
708
761
|
|
|
762
|
+
// Case 3: Function call이 없는 기타 경우
|
|
709
763
|
if (!executedFunctionCall) {
|
|
710
764
|
debugLog(`COMPLETION: No function call executed (other cases), breaking loop`);
|
|
765
|
+
if (pendingMessageOutput) {
|
|
766
|
+
debugLog(`Displaying pending message before completion`);
|
|
767
|
+
processMessageOutput(pendingMessageOutput, true);
|
|
768
|
+
}
|
|
711
769
|
break;
|
|
712
770
|
}
|
|
713
771
|
|
|
772
|
+
// Function call 실행 후 계속 진행하기 전에 pending message가 있으면 출력
|
|
773
|
+
// (function_call과 message가 같이 온 경우)
|
|
774
|
+
if (pendingMessageOutput) {
|
|
775
|
+
debugLog(`Displaying pending message before continuing (function_call + message case)`);
|
|
776
|
+
processMessageOutput(pendingMessageOutput, true);
|
|
777
|
+
}
|
|
778
|
+
|
|
714
779
|
// continueOrchestratorConversation 호출
|
|
715
780
|
debugLog(`Continuing orchestrator conversation for next iteration...`);
|
|
716
781
|
try {
|
|
@@ -793,14 +858,17 @@ export async function runSession(options) {
|
|
|
793
858
|
if (!sessionInterrupted) {
|
|
794
859
|
sessionInterrupted = true;
|
|
795
860
|
abortCurrentRequest(); // API 요청 즉시 중단
|
|
796
|
-
|
|
861
|
+
safeUiEvents.addErrorMessage('Session interrupted by user');
|
|
797
862
|
debugLog('[runSession] Session interrupted by user');
|
|
798
863
|
}
|
|
799
864
|
};
|
|
800
|
-
|
|
865
|
+
safeUiEvents.on('session:interrupt', handleInterrupt);
|
|
801
866
|
|
|
802
867
|
// 세션 시작 알림
|
|
803
|
-
|
|
868
|
+
safeUiEvents.sessionStart('Running agent session...');
|
|
869
|
+
|
|
870
|
+
// catch 블록에서도 접근 가능하도록 try 블록 밖에서 선언
|
|
871
|
+
let currentSessionData = null;
|
|
804
872
|
|
|
805
873
|
try {
|
|
806
874
|
// 현재 세션 데이터 생성
|
|
@@ -818,7 +886,7 @@ export async function runSession(options) {
|
|
|
818
886
|
// previousSessions가 제공되지 않은 경우 자동으로 로드
|
|
819
887
|
const sessionsToUse = previousSessions ?? await loadPreviousSessions(process.app_custom.sessionID);
|
|
820
888
|
|
|
821
|
-
|
|
889
|
+
currentSessionData = createSessionData(process.app_custom.sessionID, mission);
|
|
822
890
|
|
|
823
891
|
// 파일 무결성 시스템에 현재 세션 ID 설정
|
|
824
892
|
setCurrentSession(process.app_custom.sessionID);
|
|
@@ -864,6 +932,11 @@ export async function runSession(options) {
|
|
|
864
932
|
restoreTodosFromSession({ currentTodos: conversationState.currentTodos });
|
|
865
933
|
debugLog(`[runSession] Restored ${conversationState.currentTodos.length} todos from previous session`);
|
|
866
934
|
}
|
|
935
|
+
// TrimmedFileReads 복원
|
|
936
|
+
if (conversationState.trimmedFileReads) {
|
|
937
|
+
restoreTrimmedFileReadsFromSession({ trimmedFileReads: conversationState.trimmedFileReads });
|
|
938
|
+
debugLog(`[runSession] Restored ${conversationState.trimmedFileReads.length} trimmed file reads from previous session`);
|
|
939
|
+
}
|
|
867
940
|
// logSuccess('✓ Conversation state restored');
|
|
868
941
|
} else {
|
|
869
942
|
// logSystem('ℹ Starting with fresh conversation state');
|
|
@@ -886,6 +959,11 @@ export async function runSession(options) {
|
|
|
886
959
|
updateCurrentTodos([]);
|
|
887
960
|
debugLog(`[runSession] Starting fresh session - no todos in memory`);
|
|
888
961
|
}
|
|
962
|
+
|
|
963
|
+
// 새 세션 시작 시 쉘의 작업 디렉토리를 프로젝트 루트로 리셋
|
|
964
|
+
// (이전 미션에서 cd한 디렉토리가 남아있지 않도록)
|
|
965
|
+
await resetShellCwd();
|
|
966
|
+
debugLog(`[runSession] Shell cwd reset to: ${process.cwd()}`);
|
|
889
967
|
}
|
|
890
968
|
|
|
891
969
|
// Python 사용 가능 여부 확인
|
|
@@ -899,8 +977,25 @@ export async function runSession(options) {
|
|
|
899
977
|
...GLOB_FUNCTIONS,
|
|
900
978
|
...RESPONSE_MESSAGE_FUNCTIONS,
|
|
901
979
|
...TODO_WRITE_FUNCTIONS,
|
|
980
|
+
...SKILL_FUNCTIONS, // 스킬 호출 도구
|
|
902
981
|
...mcpToolFunctions,
|
|
903
|
-
"bash": async (args) =>
|
|
982
|
+
"bash": async (args) => {
|
|
983
|
+
if (args.background) {
|
|
984
|
+
// 백그라운드 실행
|
|
985
|
+
const result = await runInBackground(args.script);
|
|
986
|
+
return {
|
|
987
|
+
stdout: `Background process started: ${result.id} (pid: ${result.pid})`,
|
|
988
|
+
stderr: '',
|
|
989
|
+
code: 0,
|
|
990
|
+
background: true,
|
|
991
|
+
processId: result.id,
|
|
992
|
+
pid: result.pid
|
|
993
|
+
};
|
|
994
|
+
} else {
|
|
995
|
+
// 일반 실행
|
|
996
|
+
return execShellScript(args.script);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
904
999
|
};
|
|
905
1000
|
|
|
906
1001
|
// Python이 있는 경우에만 Python 관련 도구 추가
|
|
@@ -1030,6 +1125,9 @@ export async function runSession(options) {
|
|
|
1030
1125
|
// Todos를 세션에 저장
|
|
1031
1126
|
saveTodosToSession(currentSessionData);
|
|
1032
1127
|
|
|
1128
|
+
// TrimmedFileReads를 세션에 저장
|
|
1129
|
+
saveTrimmedFileReadsToSession(currentSessionData);
|
|
1130
|
+
|
|
1033
1131
|
debugLog(`[ITERATION ${iteration_count}] Saving session to history after completion_judge (mission_solved=${mission_solved})`);
|
|
1034
1132
|
await saveSessionToHistory(currentSessionData).catch(err => {
|
|
1035
1133
|
debugLog(`[ITERATION ${iteration_count}] Failed to save session after completion_judge: ${err.message}`);
|
|
@@ -1058,10 +1156,14 @@ export async function runSession(options) {
|
|
|
1058
1156
|
currentSessionData.orchestratorConversation = getOrchestratorConversation();
|
|
1059
1157
|
currentSessionData.orchestratorRequestOptions = null;
|
|
1060
1158
|
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1159
|
+
// 인터럽트 시에도 Todos와 TrimmedFileReads 저장 (다음 세션에서 복원 가능하도록)
|
|
1160
|
+
saveTodosToSession(currentSessionData);
|
|
1161
|
+
saveTrimmedFileReadsToSession(currentSessionData);
|
|
1162
|
+
|
|
1163
|
+
debugLog(`[ITERATION ${iteration_count}] Session interrupted - saving trimmedFileReads and todos`);
|
|
1164
|
+
await saveSessionToHistory(currentSessionData).catch(err => {
|
|
1165
|
+
debugLog(`[ITERATION ${iteration_count}] Failed to save session on interrupt: ${err.message}`);
|
|
1166
|
+
});
|
|
1065
1167
|
break;
|
|
1066
1168
|
}
|
|
1067
1169
|
|
|
@@ -1093,8 +1195,23 @@ export async function runSession(options) {
|
|
|
1093
1195
|
const message = stallDetected
|
|
1094
1196
|
? '⚠ Session ended due to orchestrator stall'
|
|
1095
1197
|
: '✅ No function calls detected - mission complete';
|
|
1096
|
-
|
|
1198
|
+
safeUiEvents.addSystemMessage(message);
|
|
1097
1199
|
debugLog(`[ITERATION ${iteration_count}] ${message}`);
|
|
1200
|
+
|
|
1201
|
+
// completion_judge 없이 종료되는 경우에도 Todos와 TrimmedFileReads 저장
|
|
1202
|
+
currentSessionData.completed_at = new Date().toISOString();
|
|
1203
|
+
currentSessionData.mission_solved = true;
|
|
1204
|
+
currentSessionData.iteration_count = iteration_count;
|
|
1205
|
+
currentSessionData.toolUsageHistory = toolUsageHistory;
|
|
1206
|
+
currentSessionData.orchestratorConversation = getOrchestratorConversation();
|
|
1207
|
+
currentSessionData.orchestratorRequestOptions = null;
|
|
1208
|
+
saveTodosToSession(currentSessionData);
|
|
1209
|
+
saveTrimmedFileReadsToSession(currentSessionData);
|
|
1210
|
+
debugLog(`[ITERATION ${iteration_count}] Saving session on no-function-call exit`);
|
|
1211
|
+
await saveSessionToHistory(currentSessionData).catch(err => {
|
|
1212
|
+
debugLog(`[ITERATION ${iteration_count}] Failed to save session: ${err.message}`);
|
|
1213
|
+
});
|
|
1214
|
+
|
|
1098
1215
|
debugLog('========================================');
|
|
1099
1216
|
debugLog(`========== ITERATION ${iteration_count} END ==========`);
|
|
1100
1217
|
debugLog('========================================');
|
|
@@ -1134,12 +1251,17 @@ export async function runSession(options) {
|
|
|
1134
1251
|
currentSessionData.orchestratorConversation = result.orchestratorConversation;
|
|
1135
1252
|
currentSessionData.orchestratorRequestOptions = null; // 필요시 orchestrator에서 가져올 수 있음
|
|
1136
1253
|
|
|
1137
|
-
|
|
1254
|
+
// 루프 정상 종료 시에도 Todos와 TrimmedFileReads 저장
|
|
1255
|
+
saveTodosToSession(currentSessionData);
|
|
1256
|
+
saveTrimmedFileReadsToSession(currentSessionData);
|
|
1257
|
+
|
|
1258
|
+
debugLog(`[runSession] Saving final session to history - sessionID: ${currentSessionData.sessionID}`);
|
|
1138
1259
|
debugLog(`[runSession] Session data size: ${JSON.stringify(currentSessionData).length} bytes`);
|
|
1139
1260
|
debugLog(`[runSession] Final state - mission_solved: ${currentSessionData.mission_solved}, iteration_count: ${currentSessionData.iteration_count}`);
|
|
1140
1261
|
debugLog(`[runSession] Tool usage history entries: ${currentSessionData.toolUsageHistory.length}`);
|
|
1141
|
-
|
|
1142
|
-
|
|
1262
|
+
await saveSessionToHistory(currentSessionData).catch(err => {
|
|
1263
|
+
debugLog(`[runSession] Failed to save final session: ${err.message}`);
|
|
1264
|
+
});
|
|
1143
1265
|
|
|
1144
1266
|
debugLog('========================================');
|
|
1145
1267
|
debugLog('========== runSession END ==========');
|
|
@@ -1155,9 +1277,13 @@ export async function runSession(options) {
|
|
|
1155
1277
|
// 에러 메시지를 history에 표시 (한 번에 통합)
|
|
1156
1278
|
const detailMessage = error?.error?.message || errorMessage;
|
|
1157
1279
|
|
|
1280
|
+
// debug 설정 확인 (SHOW_API_PAYLOAD가 true면 verbose 모드)
|
|
1281
|
+
const settings = await loadSettings().catch(() => ({}));
|
|
1282
|
+
const isDebugMode = settings?.SHOW_API_PAYLOAD === true;
|
|
1283
|
+
|
|
1158
1284
|
let consolidatedErrorMessage;
|
|
1159
|
-
if (
|
|
1160
|
-
// 상세
|
|
1285
|
+
if (isDebugMode) {
|
|
1286
|
+
// 상세 모드 (/debug on): 모든 에러 정보 표시
|
|
1161
1287
|
consolidatedErrorMessage = [
|
|
1162
1288
|
`[Session] Internal session error: ${errorType}`,
|
|
1163
1289
|
` ├─ Message: ${detailMessage}`,
|
|
@@ -1168,19 +1294,48 @@ export async function runSession(options) {
|
|
|
1168
1294
|
` └─ Stack trace: ${errorStack}`
|
|
1169
1295
|
].join('\n');
|
|
1170
1296
|
} else {
|
|
1171
|
-
// 간결 모드 (
|
|
1172
|
-
|
|
1297
|
+
// 간결 모드 (기본값, /debug off): 사용자 친화적 에러 메시지
|
|
1298
|
+
const statusNum = parseInt(errorStatus) || parseInt(error?.status);
|
|
1299
|
+
let userFriendlyMessage;
|
|
1300
|
+
|
|
1301
|
+
if (statusNum === 503 || detailMessage.includes('503')) {
|
|
1302
|
+
userFriendlyMessage = 'AI 서버가 일시적으로 응답하지 않습니다. 잠시 후 다시 시도해주세요.';
|
|
1303
|
+
} else if (statusNum === 500 || detailMessage.includes('500')) {
|
|
1304
|
+
userFriendlyMessage = 'AI 서버에서 오류가 발생했습니다. 잠시 후 다시 시도해주세요.';
|
|
1305
|
+
} else if (statusNum === 401 || errorCode === 'invalid_api_key') {
|
|
1306
|
+
userFriendlyMessage = 'API 키가 유효하지 않습니다. 설정을 확인해주세요.';
|
|
1307
|
+
} else if (statusNum === 429) {
|
|
1308
|
+
userFriendlyMessage = '요청 한도를 초과했습니다. 잠시 후 다시 시도해주세요.';
|
|
1309
|
+
} else if (errorType === 'AbortError' || errorMessage.includes('aborted')) {
|
|
1310
|
+
userFriendlyMessage = '요청이 취소되었습니다.';
|
|
1311
|
+
} else {
|
|
1312
|
+
userFriendlyMessage = '오류가 발생했습니다. 문제가 지속되면 /debug on 으로 상세 정보를 확인하세요.';
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
consolidatedErrorMessage = `[Session] ${userFriendlyMessage}`;
|
|
1173
1316
|
}
|
|
1174
1317
|
|
|
1175
|
-
|
|
1318
|
+
safeUiEvents.addErrorMessage(consolidatedErrorMessage);
|
|
1319
|
+
|
|
1320
|
+
// 에러 발생 시에도 Todos와 TrimmedFileReads 저장 시도
|
|
1321
|
+
if (currentSessionData) {
|
|
1322
|
+
currentSessionData.completed_at = new Date().toISOString();
|
|
1323
|
+
currentSessionData.mission_solved = false;
|
|
1324
|
+
saveTodosToSession(currentSessionData);
|
|
1325
|
+
saveTrimmedFileReadsToSession(currentSessionData);
|
|
1326
|
+
debugLog(`[runSession] Saving session on error`);
|
|
1327
|
+
await saveSessionToHistory(currentSessionData).catch(err => {
|
|
1328
|
+
debugLog(`[runSession] Failed to save session on error: ${err.message}`);
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
1176
1331
|
|
|
1177
1332
|
// 에러를 throw하지 않고 정상적으로 종료
|
|
1178
1333
|
return null;
|
|
1179
1334
|
} finally {
|
|
1180
1335
|
// 세션 중단 이벤트 리스너 제거
|
|
1181
|
-
|
|
1336
|
+
safeUiEvents.off('session:interrupt', handleInterrupt);
|
|
1182
1337
|
|
|
1183
1338
|
// 세션 종료 알림
|
|
1184
|
-
|
|
1339
|
+
safeUiEvents.sessionEnd();
|
|
1185
1340
|
}
|
|
1186
1341
|
}
|
|
@@ -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
|
+
}
|