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.
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
@@ -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 { ERROR_VERBOSITY } from '../config/feature_flags.js';
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
- const MAX_REASONING_ONLY_RESPONSES = 5;
31
- const DEFAULT_MAX_ITERATIONS = 50;
32
- const SUB_MISSION_MAX_LENGTH = 120;
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
- uiEvents.addSystemMessage('⚠ Orchestrator stalled (reasoning only) - treating as completion');
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
- if (!executedFunctionCall && lastOutputType === 'message') {
602
- debugLog(`COMPLETION: No function call + message type - judging if mission is truly complete`);
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, displaying pending message and breaking loop`);
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
- debugLog(`Mission not complete, continuing without displaying message`);
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; // auto-generated 플래그 설정
731
+ improvementPointsIsAutoGenerated = true;
679
732
  debugLog(`[_internal_only] Set improvementPointsIsAutoGenerated=true for whatUserShouldSay`);
680
- missionSolved = false; // 완료되지 않았으므로 상위 루프 계속
681
- break; // processOrchestratorResponses 루프 종료
733
+ missionSolved = false;
734
+ break;
682
735
  } else {
683
736
  // whatUserShouldSay가 없으면 빈 문자열로 계속 진행
684
737
  improvementPoints = "";
685
738
  debugLog(`Continuing mission without whatUserShouldSay`);
686
739
 
687
- // 세션 중단 확인 (continueOrchestratorConversation 호출 전)
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
- uiEvents.addErrorMessage('Session interrupted by user');
861
+ safeUiEvents.addErrorMessage('Session interrupted by user');
797
862
  debugLog('[runSession] Session interrupted by user');
798
863
  }
799
864
  };
800
- uiEvents.on('session:interrupt', handleInterrupt);
865
+ safeUiEvents.on('session:interrupt', handleInterrupt);
801
866
 
802
867
  // 세션 시작 알림
803
- uiEvents.sessionStart('Running agent session...');
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
- const currentSessionData = createSessionData(process.app_custom.sessionID, mission);
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) => execShellScript(args.script)
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
- debugLog(`[ITERATION ${iteration_count}] Session interrupted - NOT saving session to history (disabled)`);
1062
- // await saveSessionToHistory(currentSessionData).catch(err => {
1063
- // debugLog(`[ITERATION ${iteration_count}] Failed to save session: ${err.message}`);
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
- uiEvents.addSystemMessage(message);
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
- debugLog(`[runSession] NOT saving final session to history (disabled - will only save after completion_judge) - sessionID: ${currentSessionData.sessionID}`);
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
- // 최종 세션 히스토리에 저장 (중복 저장이지만 최신 상태 보장) - DISABLED
1142
- // await saveSessionToHistory(currentSessionData);
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 (ERROR_VERBOSITY === 'verbose') {
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
- consolidatedErrorMessage = `[Session] Internal session error: ${detailMessage}`;
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
- uiEvents.addErrorMessage(consolidatedErrorMessage);
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
- uiEvents.off('session:interrupt', handleInterrupt);
1336
+ safeUiEvents.off('session:interrupt', handleInterrupt);
1182
1337
 
1183
1338
  // 세션 종료 알림
1184
- uiEvents.sessionEnd();
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
+ }