aiexecode 1.0.126 → 1.0.128

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.
@@ -1,35 +1,70 @@
1
1
  /**
2
2
  * Output Helper - UI 이벤트 발생
3
+ * Pipe mode에서는 stderr로 출력하거나 무시
3
4
  */
4
5
 
5
6
  import { uiEvents } from './ui_events.js';
6
7
 
8
+ // Pipe mode 감지
9
+ const isPipeMode = () => process.app_custom?.pipeMode === true;
10
+
7
11
  export function logSystem(message) {
8
- uiEvents.addSystemMessage(message);
12
+ if (isPipeMode()) {
13
+ console.error(`[SYSTEM] ${message}`);
14
+ } else {
15
+ uiEvents.addSystemMessage(message);
16
+ }
9
17
  }
10
18
 
11
19
  export function logError(message) {
12
- uiEvents.addErrorMessage(message);
20
+ if (isPipeMode()) {
21
+ console.error(`[ERROR] ${message}`);
22
+ } else {
23
+ uiEvents.addErrorMessage(message);
24
+ }
13
25
  }
14
26
 
15
27
  export function logAssistantMessage(message) {
16
- uiEvents.addAssistantMessage(message);
28
+ if (isPipeMode()) {
29
+ console.error(`[ASSISTANT] ${message}`);
30
+ } else {
31
+ uiEvents.addAssistantMessage(message);
32
+ }
17
33
  }
18
34
 
19
35
  export function logToolCall(toolName, args) {
20
- uiEvents.startToolExecution(toolName, args);
36
+ if (isPipeMode()) {
37
+ console.error(`[TOOL] ${toolName}`);
38
+ } else {
39
+ uiEvents.startToolExecution(toolName, args);
40
+ }
21
41
  }
22
42
 
23
43
  export function logToolResult(toolName, stdout, originalResult = null, toolInput = null, fileSnapshot = null) {
24
- uiEvents.addToolResult(toolName, { stdout, originalResult, fileSnapshot }, toolInput);
44
+ if (isPipeMode()) {
45
+ // Pipe mode에서는 도구 결과를 간략하게 stderr로
46
+ const preview = stdout?.substring(0, 200) || '';
47
+ console.error(`[TOOL_RESULT] ${toolName}: ${preview}${stdout?.length > 200 ? '...' : ''}`);
48
+ } else {
49
+ uiEvents.addToolResult(toolName, { stdout, originalResult, fileSnapshot }, toolInput);
50
+ }
25
51
  }
26
52
 
27
53
  export function logCodeExecution(language, code) {
28
- uiEvents.startCodeExecution(language, code);
54
+ if (isPipeMode()) {
55
+ console.error(`[CODE] ${language}`);
56
+ } else {
57
+ uiEvents.startCodeExecution(language, code);
58
+ }
29
59
  }
30
60
 
31
61
  export function logCodeResult(stdout, stderr, exitCode) {
32
- uiEvents.addCodeResult(stdout, stderr, exitCode);
62
+ if (isPipeMode()) {
63
+ if (stdout) console.error(`[CODE_STDOUT] ${stdout.substring(0, 200)}${stdout.length > 200 ? '...' : ''}`);
64
+ if (stderr) console.error(`[CODE_STDERR] ${stderr.substring(0, 200)}${stderr.length > 200 ? '...' : ''}`);
65
+ } else {
66
+ uiEvents.addCodeResult(stdout, stderr, exitCode);
67
+ }
33
68
  }
34
69
 
35
70
  export function logIteration(iterationNumber) {
@@ -38,9 +73,17 @@ export function logIteration(iterationNumber) {
38
73
  }
39
74
 
40
75
  export function logMissionComplete() {
41
- uiEvents.missionCompleted(0);
76
+ if (isPipeMode()) {
77
+ console.error('[COMPLETE] Mission completed');
78
+ } else {
79
+ uiEvents.missionCompleted(0);
80
+ }
42
81
  }
43
82
 
44
83
  export function logConversationRestored(count) {
45
- uiEvents.conversationRestored(count);
84
+ if (isPipeMode()) {
85
+ console.error(`[RESTORED] ${count} conversation(s) restored`);
86
+ } else {
87
+ uiEvents.conversationRestored(count);
88
+ }
46
89
  }
@@ -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, getPersistentShell } 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";
@@ -30,6 +31,47 @@ import {
30
31
 
31
32
  const debugLog = createDebugLogger('session.log', 'session');
32
33
 
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
+ };
74
+
33
75
 
34
76
  /**
35
77
  * 서브미션 이름을 추론하는 헬퍼 함수
@@ -397,7 +439,7 @@ async function processOrchestratorResponses(params) {
397
439
  debugLog(`No processable output, reasoningOnlyResponses: ${reasoningOnlyResponses}`);
398
440
  if (reasoningOnlyResponses >= MAX_REASONING_ONLY_RESPONSES) {
399
441
  debugLog(`Reached MAX_REASONING_ONLY_RESPONSES (${MAX_REASONING_ONLY_RESPONSES}), treating as stall`);
400
- uiEvents.addSystemMessage('⚠ Orchestrator stalled (reasoning only) - treating as completion');
442
+ safeUiEvents.addSystemMessage('⚠ Orchestrator stalled (reasoning only) - treating as completion');
401
443
  stallDetected = true;
402
444
  break;
403
445
  }
@@ -816,14 +858,14 @@ export async function runSession(options) {
816
858
  if (!sessionInterrupted) {
817
859
  sessionInterrupted = true;
818
860
  abortCurrentRequest(); // API 요청 즉시 중단
819
- uiEvents.addErrorMessage('Session interrupted by user');
861
+ safeUiEvents.addErrorMessage('Session interrupted by user');
820
862
  debugLog('[runSession] Session interrupted by user');
821
863
  }
822
864
  };
823
- uiEvents.on('session:interrupt', handleInterrupt);
865
+ safeUiEvents.on('session:interrupt', handleInterrupt);
824
866
 
825
867
  // 세션 시작 알림
826
- uiEvents.sessionStart('Running agent session...');
868
+ safeUiEvents.sessionStart('Running agent session...');
827
869
 
828
870
  // catch 블록에서도 접근 가능하도록 try 블록 밖에서 선언
829
871
  let currentSessionData = null;
@@ -895,6 +937,11 @@ export async function runSession(options) {
895
937
  restoreTrimmedFileReadsFromSession({ trimmedFileReads: conversationState.trimmedFileReads });
896
938
  debugLog(`[runSession] Restored ${conversationState.trimmedFileReads.length} trimmed file reads from previous session`);
897
939
  }
940
+ // Shell CWD 복원 (세션 이어가기 시 이전 작업 디렉토리 유지)
941
+ if (conversationState.shellCwd) {
942
+ await resetShellCwd(conversationState.shellCwd);
943
+ debugLog(`[runSession] Restored shell cwd to: ${conversationState.shellCwd}`);
944
+ }
898
945
  // logSuccess('✓ Conversation state restored');
899
946
  } else {
900
947
  // logSystem('ℹ Starting with fresh conversation state');
@@ -917,6 +964,11 @@ export async function runSession(options) {
917
964
  updateCurrentTodos([]);
918
965
  debugLog(`[runSession] Starting fresh session - no todos in memory`);
919
966
  }
967
+
968
+ // 새 세션 시작 시 쉘의 작업 디렉토리를 프로젝트 루트로 리셋
969
+ // (이전 미션에서 cd한 디렉토리가 남아있지 않도록)
970
+ await resetShellCwd();
971
+ debugLog(`[runSession] Shell cwd reset to: ${process.cwd()}`);
920
972
  }
921
973
 
922
974
  // Python 사용 가능 여부 확인
@@ -932,7 +984,23 @@ export async function runSession(options) {
932
984
  ...TODO_WRITE_FUNCTIONS,
933
985
  ...SKILL_FUNCTIONS, // 스킬 호출 도구
934
986
  ...mcpToolFunctions,
935
- "bash": async (args) => execShellScript(args.script)
987
+ "bash": async (args) => {
988
+ if (args.background) {
989
+ // 백그라운드 실행
990
+ const result = await runInBackground(args.script);
991
+ return {
992
+ stdout: `Background process started: ${result.id} (pid: ${result.pid})`,
993
+ stderr: '',
994
+ code: 0,
995
+ background: true,
996
+ processId: result.id,
997
+ pid: result.pid
998
+ };
999
+ } else {
1000
+ // 일반 실행
1001
+ return execShellScript(args.script);
1002
+ }
1003
+ }
936
1004
  };
937
1005
 
938
1006
  // Python이 있는 경우에만 Python 관련 도구 추가
@@ -1059,6 +1127,9 @@ export async function runSession(options) {
1059
1127
  currentSessionData.orchestratorConversation = getOrchestratorConversation();
1060
1128
  currentSessionData.orchestratorRequestOptions = null;
1061
1129
 
1130
+ // Shell CWD를 세션에 저장 (세션 이어가기 시 복원용)
1131
+ currentSessionData.shellCwd = await getPersistentShell().getCwd();
1132
+
1062
1133
  // Todos를 세션에 저장
1063
1134
  saveTodosToSession(currentSessionData);
1064
1135
 
@@ -1093,6 +1164,9 @@ export async function runSession(options) {
1093
1164
  currentSessionData.orchestratorConversation = getOrchestratorConversation();
1094
1165
  currentSessionData.orchestratorRequestOptions = null;
1095
1166
 
1167
+ // Shell CWD를 세션에 저장
1168
+ currentSessionData.shellCwd = await getPersistentShell().getCwd();
1169
+
1096
1170
  // 인터럽트 시에도 Todos와 TrimmedFileReads 저장 (다음 세션에서 복원 가능하도록)
1097
1171
  saveTodosToSession(currentSessionData);
1098
1172
  saveTrimmedFileReadsToSession(currentSessionData);
@@ -1132,7 +1206,7 @@ export async function runSession(options) {
1132
1206
  const message = stallDetected
1133
1207
  ? '⚠ Session ended due to orchestrator stall'
1134
1208
  : '✅ No function calls detected - mission complete';
1135
- uiEvents.addSystemMessage(message);
1209
+ safeUiEvents.addSystemMessage(message);
1136
1210
  debugLog(`[ITERATION ${iteration_count}] ${message}`);
1137
1211
 
1138
1212
  // completion_judge 없이 종료되는 경우에도 Todos와 TrimmedFileReads 저장
@@ -1142,6 +1216,7 @@ export async function runSession(options) {
1142
1216
  currentSessionData.toolUsageHistory = toolUsageHistory;
1143
1217
  currentSessionData.orchestratorConversation = getOrchestratorConversation();
1144
1218
  currentSessionData.orchestratorRequestOptions = null;
1219
+ currentSessionData.shellCwd = await getPersistentShell().getCwd();
1145
1220
  saveTodosToSession(currentSessionData);
1146
1221
  saveTrimmedFileReadsToSession(currentSessionData);
1147
1222
  debugLog(`[ITERATION ${iteration_count}] Saving session on no-function-call exit`);
@@ -1188,6 +1263,9 @@ export async function runSession(options) {
1188
1263
  currentSessionData.orchestratorConversation = result.orchestratorConversation;
1189
1264
  currentSessionData.orchestratorRequestOptions = null; // 필요시 orchestrator에서 가져올 수 있음
1190
1265
 
1266
+ // Shell CWD를 세션에 저장
1267
+ currentSessionData.shellCwd = await getPersistentShell().getCwd();
1268
+
1191
1269
  // 루프 정상 종료 시에도 Todos와 TrimmedFileReads 저장
1192
1270
  saveTodosToSession(currentSessionData);
1193
1271
  saveTrimmedFileReadsToSession(currentSessionData);
@@ -1252,12 +1330,13 @@ export async function runSession(options) {
1252
1330
  consolidatedErrorMessage = `[Session] ${userFriendlyMessage}`;
1253
1331
  }
1254
1332
 
1255
- uiEvents.addErrorMessage(consolidatedErrorMessage);
1333
+ safeUiEvents.addErrorMessage(consolidatedErrorMessage);
1256
1334
 
1257
1335
  // 에러 발생 시에도 Todos와 TrimmedFileReads 저장 시도
1258
1336
  if (currentSessionData) {
1259
1337
  currentSessionData.completed_at = new Date().toISOString();
1260
1338
  currentSessionData.mission_solved = false;
1339
+ currentSessionData.shellCwd = await getPersistentShell().getCwd();
1261
1340
  saveTodosToSession(currentSessionData);
1262
1341
  saveTrimmedFileReadsToSession(currentSessionData);
1263
1342
  debugLog(`[runSession] Saving session on error`);
@@ -1270,9 +1349,9 @@ export async function runSession(options) {
1270
1349
  return null;
1271
1350
  } finally {
1272
1351
  // 세션 중단 이벤트 리스너 제거
1273
- uiEvents.off('session:interrupt', handleInterrupt);
1352
+ safeUiEvents.off('session:interrupt', handleInterrupt);
1274
1353
 
1275
1354
  // 세션 종료 알림
1276
- uiEvents.sessionEnd();
1355
+ safeUiEvents.sessionEnd();
1277
1356
  }
1278
1357
  }
@@ -152,7 +152,8 @@ export function getLastConversationState(sessions) {
152
152
  orchestratorConversation: lastSession.orchestratorConversation || [],
153
153
  orchestratorRequestOptions: lastSession.orchestratorRequestOptions || null,
154
154
  currentTodos: lastSession.currentTodos || [],
155
- trimmedFileReads: lastSession.trimmedFileReads || []
155
+ trimmedFileReads: lastSession.trimmedFileReads || [],
156
+ shellCwd: lastSession.shellCwd || null
156
157
  };
157
158
  }
158
159
 
@@ -1,4 +1,6 @@
1
1
  import { uiEvents } from '../system/ui_events.js';
2
+ import { closePersistentShell } from '../system/code_executer.js';
3
+ import { killAllBackgroundProcesses } from '../system/background_process.js';
2
4
 
3
5
  /**
4
6
  * Unified exit handler for the application
@@ -17,6 +19,12 @@ export async function performExit(options = {}) {
17
19
  uiEvents.addSystemMessage(`Goodbye! Session ID: ${sessionID}`);
18
20
  }
19
21
 
22
+ // 백그라운드 프로세스 정리
23
+ await killAllBackgroundProcesses();
24
+
25
+ // PersistentShell 정리
26
+ await closePersistentShell();
27
+
20
28
  // MCP Integration 정리
21
29
  if (mcpIntegration) {
22
30
  await mcpIntegration.cleanup();