aiexecode 1.0.94 → 1.0.127

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of aiexecode might be problematic. Click here for more details.

Files changed (80) hide show
  1. package/README.md +198 -88
  2. package/index.js +310 -86
  3. package/mcp-agent-lib/src/mcp_message_logger.js +17 -16
  4. package/package.json +4 -4
  5. package/payload_viewer/out/404/index.html +1 -1
  6. package/payload_viewer/out/404.html +1 -1
  7. package/payload_viewer/out/_next/static/chunks/{37d0cd2587a38f79.js → b6c0459f3789d25c.js} +1 -1
  8. package/payload_viewer/out/_next/static/chunks/b75131b58f8ca46a.css +3 -0
  9. package/payload_viewer/out/index.html +1 -1
  10. package/payload_viewer/out/index.txt +3 -3
  11. package/payload_viewer/web_server.js +361 -0
  12. package/prompts/completion_judge.txt +4 -0
  13. package/prompts/orchestrator.txt +116 -3
  14. package/src/LLMClient/client.js +401 -18
  15. package/src/LLMClient/converters/responses-to-claude.js +67 -18
  16. package/src/LLMClient/converters/responses-to-zai.js +667 -0
  17. package/src/LLMClient/errors.js +30 -4
  18. package/src/LLMClient/index.js +5 -0
  19. package/src/ai_based/completion_judge.js +263 -186
  20. package/src/ai_based/orchestrator.js +171 -35
  21. package/src/commands/agents.js +70 -0
  22. package/src/commands/apikey.js +1 -1
  23. package/src/commands/bg.js +129 -0
  24. package/src/commands/commands.js +51 -0
  25. package/src/commands/debug.js +52 -0
  26. package/src/commands/help.js +11 -1
  27. package/src/commands/model.js +42 -7
  28. package/src/commands/reasoning_effort.js +2 -2
  29. package/src/commands/skills.js +46 -0
  30. package/src/config/ai_models.js +106 -6
  31. package/src/config/constants.js +71 -0
  32. package/src/config/feature_flags.js +6 -7
  33. package/src/frontend/App.js +108 -1
  34. package/src/frontend/components/AutocompleteMenu.js +7 -1
  35. package/src/frontend/components/BackgroundProcessList.js +175 -0
  36. package/src/frontend/components/ConversationItem.js +26 -10
  37. package/src/frontend/components/CurrentModelView.js +2 -2
  38. package/src/frontend/components/HelpView.js +106 -2
  39. package/src/frontend/components/Input.js +33 -11
  40. package/src/frontend/components/ModelListView.js +1 -1
  41. package/src/frontend/components/SetupWizard.js +51 -8
  42. package/src/frontend/hooks/useFileCompletion.js +467 -0
  43. package/src/frontend/utils/toolUIFormatter.js +261 -0
  44. package/src/system/agents_loader.js +289 -0
  45. package/src/system/ai_request.js +156 -12
  46. package/src/system/background_process.js +317 -0
  47. package/src/system/code_executer.js +496 -56
  48. package/src/system/command_parser.js +33 -3
  49. package/src/system/conversation_state.js +265 -0
  50. package/src/system/conversation_trimmer.js +132 -0
  51. package/src/system/custom_command_loader.js +386 -0
  52. package/src/system/file_integrity.js +73 -10
  53. package/src/system/log.js +10 -2
  54. package/src/system/output_helper.js +52 -9
  55. package/src/system/session.js +213 -58
  56. package/src/system/session_memory.js +30 -2
  57. package/src/system/skill_loader.js +318 -0
  58. package/src/system/system_info.js +254 -40
  59. package/src/system/tool_approval.js +10 -0
  60. package/src/system/tool_registry.js +15 -1
  61. package/src/system/ui_events.js +11 -0
  62. package/src/tools/code_editor.js +16 -10
  63. package/src/tools/file_reader.js +66 -9
  64. package/src/tools/glob.js +0 -3
  65. package/src/tools/ripgrep.js +5 -7
  66. package/src/tools/skill_tool.js +122 -0
  67. package/src/tools/web_downloader.js +0 -3
  68. package/src/util/clone.js +174 -0
  69. package/src/util/config.js +55 -2
  70. package/src/util/config_migration.js +174 -0
  71. package/src/util/debug_log.js +8 -2
  72. package/src/util/exit_handler.js +8 -0
  73. package/src/util/file_reference_parser.js +132 -0
  74. package/src/util/path_validator.js +178 -0
  75. package/src/util/prompt_loader.js +91 -1
  76. package/src/util/safe_fs.js +66 -3
  77. package/payload_viewer/out/_next/static/chunks/ecd2072ebf41611f.css +0 -3
  78. /package/payload_viewer/out/_next/static/{wkEKh6i9XPSyP6rjDRvHn → 42iEoi-1o5MxNIZ1SWSvV}/_buildManifest.js +0 -0
  79. /package/payload_viewer/out/_next/static/{wkEKh6i9XPSyP6rjDRvHn → 42iEoi-1o5MxNIZ1SWSvV}/_clientMiddlewareManifest.json +0 -0
  80. /package/payload_viewer/out/_next/static/{wkEKh6i9XPSyP6rjDRvHn → 42iEoi-1o5MxNIZ1SWSvV}/_ssgManifest.js +0 -0
@@ -1,8 +1,8 @@
1
1
  import React from 'react';
2
2
  import { uiEvents } from '../system/ui_events.js';
3
- import { loadSettings, saveSettings, SETTINGS_FILE } from '../util/config.js';
3
+ import { loadSettings, saveSettings, SETTINGS_FILE, getDefaultBaseUrlForProvider } from '../util/config.js';
4
4
  import { resetAIClients } from '../system/ai_request.js';
5
- import { AI_MODELS, getModelsByProvider, DEFAULT_MODEL } from '../config/ai_models.js';
5
+ import { AI_MODELS, getModelsByProvider, DEFAULT_MODEL, ENABLED_PROVIDERS, isModelEnabled } from '../config/ai_models.js';
6
6
  import { renderInkComponent } from '../frontend/utils/renderInkComponent.js';
7
7
  import { ModelListView } from '../frontend/components/ModelListView.js';
8
8
  import { CurrentModelView } from '../frontend/components/CurrentModelView.js';
@@ -14,10 +14,10 @@ function getProviderForModel(modelId) {
14
14
  return modelInfo ? modelInfo.provider : null;
15
15
  }
16
16
 
17
- // 모든 모델 목록 표시
17
+ // 모든 모델 목록 표시 (공식 지원 provider만)
18
18
  async function listAllModels() {
19
- // 모든 provider 목록 추출
20
- const providers = [...new Set(Object.values(AI_MODELS).map(m => m.provider))];
19
+ // 공식 지원 provider 사용
20
+ const providers = ENABLED_PROVIDERS;
21
21
 
22
22
  // provider별로 모델 그룹화
23
23
  const modelsByProvider = {};
@@ -63,7 +63,7 @@ async function showCurrentModel() {
63
63
  */
64
64
  export default {
65
65
  name: 'model',
66
- description: 'Select AI model (OpenAI)',
66
+ description: 'Select AI model',
67
67
  usage: '/model [model-id] or /model list',
68
68
  handler: async (args, context) => {
69
69
  // 인자가 없으면 현재 모델 표시
@@ -94,6 +94,16 @@ export default {
94
94
  return;
95
95
  }
96
96
 
97
+ // 공식 지원 provider 확인
98
+ if (!isModelEnabled(modelId)) {
99
+ uiEvents.addSystemMessage(
100
+ `Model not available: ${modelId}\n\n` +
101
+ `This model is not officially supported.\n` +
102
+ `Use \`/model list\` to see available models.`
103
+ );
104
+ return;
105
+ }
106
+
97
107
  try {
98
108
  // 현재 설정 로드
99
109
  const settings = await loadSettings();
@@ -102,6 +112,20 @@ export default {
102
112
  settings.MODEL = modelId;
103
113
  process.env.MODEL = modelId;
104
114
 
115
+ // Provider별 BASE_URL 자동 설정
116
+ const defaultBaseUrl = getDefaultBaseUrlForProvider(provider);
117
+ if (defaultBaseUrl) {
118
+ // Z.AI 등 특정 provider는 BASE_URL 자동 설정
119
+ settings.BASE_URL = defaultBaseUrl;
120
+ process.env.BASE_URL = defaultBaseUrl;
121
+ } else if (provider !== 'zai') {
122
+ // 다른 provider로 변경 시 BASE_URL 초기화 (Z.AI에서 다른 모델로 변경)
123
+ if (settings.BASE_URL === 'https://api.z.ai/api/anthropic') {
124
+ settings.BASE_URL = '';
125
+ delete process.env.BASE_URL;
126
+ }
127
+ }
128
+
105
129
  // 설정 저장
106
130
  await saveSettings(settings);
107
131
 
@@ -114,11 +138,22 @@ export default {
114
138
  // 성공 메시지
115
139
  const modelInfo = AI_MODELS[modelId];
116
140
 
141
+ // Provider별 경고 메시지 구성
117
142
  let warning = null;
118
143
  if (!settings.API_KEY) {
144
+ // Provider별 API 키 안내 (공식 지원 provider만)
145
+ const apiKeyHints = {
146
+ 'zai': 'Set your Z.AI API key with: `/apikey <your-key>`'
147
+ };
119
148
  warning = {
120
149
  message: 'API key is not configured.',
121
- hint: 'Set your API key with: `/apikey sk-proj-...`'
150
+ hint: apiKeyHints[provider] || 'Set your API key with: `/apikey <your-key>`'
151
+ };
152
+ } else if (provider === 'zai') {
153
+ // Z.AI 모델 선택 시 추가 안내
154
+ warning = {
155
+ message: `BASE_URL automatically set to: ${defaultBaseUrl}`,
156
+ hint: 'Ensure your API key is valid for Z.AI service.'
122
157
  };
123
158
  }
124
159
 
@@ -105,11 +105,11 @@ function listEffortLevels(currentModel) {
105
105
  }
106
106
 
107
107
  /**
108
- * /reasoning_effort 커맨드 - OpenAI reasoning_effort 설정
108
+ * /reasoning_effort 커맨드 - reasoning_effort 설정
109
109
  */
110
110
  export default {
111
111
  name: 'reasoning_effort',
112
- description: 'Set reasoning effort for OpenAI models (minimal, low, medium, high)',
112
+ description: 'Set reasoning effort for supported models (minimal, low, medium, high)',
113
113
  usage: '/reasoning_effort [level] or /reasoning_effort list',
114
114
  handler: async (args, context) => {
115
115
  // 모델 지원 확인
@@ -0,0 +1,46 @@
1
+ import { uiEvents } from '../system/ui_events.js';
2
+ import { formatSkillList, findSkillByName, loadSkillAsPrompt } from '../system/skill_loader.js';
3
+
4
+ /**
5
+ * /skills 커맨드 - 스킬 목록 조회 및 스킬 정보 확인
6
+ *
7
+ * 사용법:
8
+ * /skills - 모든 스킬 목록 표시
9
+ * /skills <name> - 특정 스킬 상세 정보 표시
10
+ */
11
+ export default {
12
+ name: 'skills',
13
+ description: 'List available skills or show skill details',
14
+ usage: '/skills [skill-name]',
15
+ handler: async (args, context) => {
16
+ if (!args || args.length === 0) {
17
+ // 스킬 목록 표시
18
+ const skillList = await formatSkillList();
19
+ uiEvents.addSystemMessage(skillList);
20
+ return;
21
+ }
22
+
23
+ // 특정 스킬 정보 표시
24
+ const skillName = args[0];
25
+ const skill = await findSkillByName(skillName);
26
+
27
+ if (!skill) {
28
+ uiEvents.addSystemMessage(`Skill not found: ${skillName}\n\nUse /skills to see available skills.`);
29
+ return;
30
+ }
31
+
32
+ const info = [
33
+ `Skill: ${skill.name}`,
34
+ `Source: ${skill.source}`,
35
+ `Path: ${skill.path}`,
36
+ '',
37
+ 'Frontmatter:',
38
+ JSON.stringify(skill.frontmatter, null, 2),
39
+ '',
40
+ 'Content Preview:',
41
+ skill.content.substring(0, 500) + (skill.content.length > 500 ? '...' : '')
42
+ ].join('\n');
43
+
44
+ uiEvents.addSystemMessage(info);
45
+ }
46
+ };
@@ -5,6 +5,13 @@
5
5
  * 새 모델 추가 시 이 파일만 수정하면 됩니다.
6
6
  */
7
7
 
8
+ /**
9
+ * 공식 지원 Provider 목록
10
+ * 런타임에서 이 목록에 포함된 provider의 모델만 노출됩니다.
11
+ * 다른 provider의 구현 코드는 유지되지만 사용자에게 노출되지 않습니다.
12
+ */
13
+ export const ENABLED_PROVIDERS = ['zai'];
14
+
8
15
  export const AI_MODELS = {
9
16
  // ========================================
10
17
  // Claude 시리즈
@@ -62,6 +69,49 @@ export const AI_MODELS = {
62
69
  maxTokens: 4096,
63
70
  },
64
71
 
72
+ // ========================================
73
+ // Z.AI GLM 시리즈 (Anthropic API 호환)
74
+ // - interleaved-thinking 지원
75
+ // - 프롬프트 캐싱 지원 (cache_read_input_tokens)
76
+ // - 서버 측 웹 검색 지원
77
+ // ========================================
78
+ 'glm-4.7': {
79
+ provider: 'zai',
80
+ name: 'GLM-4.7',
81
+ contextWindow: 200000, // 200K
82
+ maxTokens: 128000, // 128K
83
+ supportsThinking: true,
84
+ supportsCaching: true,
85
+ supportsServerTools: true,
86
+ },
87
+ 'glm-4.6': {
88
+ provider: 'zai',
89
+ name: 'GLM-4.6',
90
+ contextWindow: 200000, // 200K
91
+ maxTokens: 128000, // 128K
92
+ supportsThinking: true,
93
+ supportsCaching: true,
94
+ supportsServerTools: true,
95
+ },
96
+ 'glm-4.5-air': {
97
+ provider: 'zai',
98
+ name: 'GLM-4.5 Air',
99
+ contextWindow: 128000, // 128K
100
+ maxTokens: 96000, // 96K
101
+ supportsThinking: true,
102
+ supportsCaching: true,
103
+ supportsServerTools: true,
104
+ },
105
+ 'glm-4.5': {
106
+ provider: 'zai',
107
+ name: 'GLM-4.5',
108
+ contextWindow: 128000, // 128K
109
+ maxTokens: 96000, // 96K
110
+ supportsThinking: true,
111
+ supportsCaching: true,
112
+ supportsServerTools: true,
113
+ },
114
+
65
115
  // ========================================
66
116
  // Google Gemini 시리즈
67
117
  // ========================================
@@ -137,6 +187,14 @@ export function getModelInfo(modelId) {
137
187
  return AI_MODELS[modelId] || null;
138
188
  }
139
189
 
190
+ /**
191
+ * 모델이 공식 지원 provider인지 확인
192
+ */
193
+ export function isModelEnabled(modelId) {
194
+ const model = AI_MODELS[modelId];
195
+ return model ? ENABLED_PROVIDERS.includes(model.provider) : false;
196
+ }
197
+
140
198
  /**
141
199
  * 모델 ID로 max_tokens 가져오기
142
200
  */
@@ -154,16 +212,21 @@ export function getContextWindow(modelId) {
154
212
  }
155
213
 
156
214
  /**
157
- * 모든 모델 ID 목록 가져오기
215
+ * 모든 모델 ID 목록 가져오기 (공식 지원 provider만)
158
216
  */
159
217
  export function getAllModelIds() {
160
- return Object.keys(AI_MODELS);
218
+ return Object.keys(AI_MODELS).filter(
219
+ modelId => ENABLED_PROVIDERS.includes(AI_MODELS[modelId].provider)
220
+ );
161
221
  }
162
222
 
163
223
  /**
164
- * 특정 제조사의 모델 ID 목록 가져오기
224
+ * 특정 제조사의 모델 ID 목록 가져오기 (공식 지원 provider만)
165
225
  */
166
226
  export function getModelsByProvider(provider) {
227
+ if (!ENABLED_PROVIDERS.includes(provider)) {
228
+ return [];
229
+ }
167
230
  return Object.keys(AI_MODELS).filter(
168
231
  modelId => AI_MODELS[modelId].provider === provider
169
232
  );
@@ -177,14 +240,51 @@ export function getGPT5Models() {
177
240
  }
178
241
 
179
242
  /**
180
- * Reasoning 지원 모델 ID 목록
243
+ * Reasoning 지원 모델 ID 목록 (공식 지원 provider만)
181
244
  */
182
245
  export function getReasoningModels() {
183
246
  return Object.keys(AI_MODELS).filter(
184
- modelId => AI_MODELS[modelId].supportsReasoning
247
+ modelId => AI_MODELS[modelId].supportsReasoning &&
248
+ ENABLED_PROVIDERS.includes(AI_MODELS[modelId].provider)
249
+ );
250
+ }
251
+
252
+ /**
253
+ * Thinking 지원 모델 ID 목록 (Z.AI GLM 등, 공식 지원 provider만)
254
+ */
255
+ export function getThinkingModels() {
256
+ return Object.keys(AI_MODELS).filter(
257
+ modelId => AI_MODELS[modelId].supportsThinking &&
258
+ ENABLED_PROVIDERS.includes(AI_MODELS[modelId].provider)
259
+ );
260
+ }
261
+
262
+ /**
263
+ * 캐싱 지원 모델 ID 목록 (공식 지원 provider만)
264
+ */
265
+ export function getCachingModels() {
266
+ return Object.keys(AI_MODELS).filter(
267
+ modelId => AI_MODELS[modelId].supportsCaching &&
268
+ ENABLED_PROVIDERS.includes(AI_MODELS[modelId].provider)
185
269
  );
186
270
  }
187
271
 
272
+ /**
273
+ * 특정 모델이 thinking을 지원하는지 확인
274
+ */
275
+ export function supportsThinking(modelId) {
276
+ const model = AI_MODELS[modelId];
277
+ return model?.supportsThinking || false;
278
+ }
279
+
280
+ /**
281
+ * 특정 모델이 캐싱을 지원하는지 확인
282
+ */
283
+ export function supportsCaching(modelId) {
284
+ const model = AI_MODELS[modelId];
285
+ return model?.supportsCaching || false;
286
+ }
287
+
188
288
  /**
189
289
  * 특정 모델이 특정 reasoning effort를 지원하는지 확인
190
290
  */
@@ -199,7 +299,7 @@ export function supportsReasoningEffort(modelId, effort) {
199
299
  /**
200
300
  * 기본 권장 모델 ID
201
301
  */
202
- export const DEFAULT_MODEL = 'gpt-5-mini';
302
+ export const DEFAULT_MODEL = 'glm-4.5';
203
303
 
204
304
  // ========================================
205
305
  // 하위 호환성을 위한 별칭 (Deprecated)
@@ -0,0 +1,71 @@
1
+ /**
2
+ * 전역 상수 정의
3
+ * 하드코딩된 값들을 중앙에서 관리합니다.
4
+ */
5
+
6
+ // ============================================
7
+ // Session 관련 상수
8
+ // ============================================
9
+
10
+ /** 서브미션 이름의 최대 길이 */
11
+ export const SUB_MISSION_MAX_LENGTH = 120;
12
+
13
+ /** 기본 최대 반복 횟수 */
14
+ export const DEFAULT_MAX_ITERATIONS = 50;
15
+
16
+ /** reasoning만 있는 응답의 최대 허용 횟수 */
17
+ export const MAX_REASONING_ONLY_RESPONSES = 5;
18
+
19
+ // ============================================
20
+ // Ripgrep 관련 상수
21
+ // ============================================
22
+
23
+ /** 기본 타임아웃 (ms) */
24
+ export const RIPGREP_DEFAULT_TIMEOUT_MS = 120000;
25
+
26
+ /** 기본 최대 매치 수 */
27
+ export const RIPGREP_DEFAULT_MAX_COUNT = 500;
28
+
29
+ /** 최대 출력 크기 (bytes) */
30
+ export const RIPGREP_MAX_OUTPUT_SIZE = 30000;
31
+
32
+ // ============================================
33
+ // File Reader 관련 상수
34
+ // ============================================
35
+
36
+ /** 파일 읽기 최대 줄 수 */
37
+ export const FILE_READER_MAX_LINES = 2000;
38
+
39
+ /** 파일 크기 제한 (bytes) - 10MB */
40
+ export const FILE_READER_MAX_SIZE_BYTES = 10 * 1024 * 1024;
41
+
42
+ // ============================================
43
+ // AI 요청 관련 상수
44
+ // ============================================
45
+
46
+ /** 컨텍스트 trim 후 재시도를 위한 메시지 최소 수 */
47
+ export const MIN_MESSAGES_FOR_RETRY = 3;
48
+
49
+ // ============================================
50
+ // UI/Output 관련 상수
51
+ // ============================================
52
+
53
+ /** 출력 클램핑 최대 길이 */
54
+ export const OUTPUT_CLAMP_MAX_LENGTH = 30000;
55
+
56
+ // ============================================
57
+ // MCP 관련 상수
58
+ // ============================================
59
+
60
+ /** MCP 서버 연결 타임아웃 (ms) */
61
+ export const MCP_CONNECTION_TIMEOUT_MS = 30000;
62
+
63
+ // ============================================
64
+ // 에러 재시도 관련 상수
65
+ // ============================================
66
+
67
+ /** 세션 저장 최대 재시도 횟수 */
68
+ export const SESSION_SAVE_MAX_RETRIES = 3;
69
+
70
+ /** 세션 저장 재시도 지연 (ms) */
71
+ export const SESSION_SAVE_RETRY_DELAY_MS = 1000;
@@ -4,13 +4,12 @@
4
4
  */
5
5
 
6
6
  /**
7
- * 에러 메시지 상세도 설정
7
+ * 에러 메시지 상세도 설정 (deprecated)
8
8
  *
9
- * 'concise': 간결한 에러 메시지만 표시 (기본값)
10
- * 'verbose': 상세한 에러 정보 표시 (코드, 스택 트레이스, 전체 에러 객체 등)
9
+ * 설정은 이상 사용되지 않습니다.
10
+ * 에러 상세도는 /debug on|off 명령으로 제어됩니다.
11
+ * - /debug off (기본): 사용자 친화적 에러 메시지
12
+ * - /debug on: 상세 에러 정보 (코드, 스택 트레이스 등)
11
13
  */
12
- export const ERROR_VERBOSITY = 'verbose';
13
14
 
14
- export default {
15
- ERROR_VERBOSITY
16
- };
15
+ export default {};
@@ -13,6 +13,7 @@ import { ConversationItem } from './components/ConversationItem.js';
13
13
  import { BlankLine } from './components/BlankLine.js';
14
14
  import { TodoList } from './components/TodoList.js';
15
15
  import { LoadingIndicator } from './components/LoadingIndicator.js';
16
+ import { BackgroundProcessList } from './components/BackgroundProcessList.js';
16
17
  import { useTextBuffer } from './utils/inputBuffer.js';
17
18
  import { uiEvents } from '../system/ui_events.js';
18
19
  import { getToolDisplayConfig, extractMessageFromArgs, formatToolCall, formatToolResult, getToolDisplayName } from '../system/tool_registry.js';
@@ -361,6 +362,9 @@ export function App({ onSubmit, onClearScreen, onExit, commands = [], model, ver
361
362
  const [approvalRequest, setApprovalRequest] = useState(null);
362
363
  const [showSetupWizard, setShowSetupWizard] = useState(false);
363
364
  const [todos, setTodos] = useState([]);
365
+ const [backgroundProcesses, setBackgroundProcesses] = useState([]);
366
+ const [showBackgroundPanel, setShowBackgroundPanel] = useState(false);
367
+ const [backgroundPanelFocused, setBackgroundPanelFocused] = useState(false);
364
368
 
365
369
  // Static items: 메모이제이션된 React 엘리먼트들 (Static 컴포넌트 제거로 스크롤 문제 해결)
366
370
  const [staticItems, setStaticItems] = useState(() => []);
@@ -888,6 +892,14 @@ export function App({ onSubmit, onClearScreen, onExit, commands = [], model, ver
888
892
  if (event.todos) {
889
893
  debugLog(`[handleTodosUpdate] Updating todos: ${event.todos.length} items`);
890
894
  setTodos(event.todos);
895
+
896
+ // 모든 todo가 completed 상태면 잠시 후 자동으로 제거
897
+ const allCompleted = event.todos.length > 0 && event.todos.every(todo => todo.status === 'completed');
898
+ if (allCompleted) {
899
+ setTimeout(() => {
900
+ setTodos([]);
901
+ }, 1500);
902
+ }
891
903
  }
892
904
  };
893
905
 
@@ -959,6 +971,94 @@ export function App({ onSubmit, onClearScreen, onExit, commands = [], model, ver
959
971
  };
960
972
  }, [addToHistory, handleSessionTransition, version]);
961
973
 
974
+ // 백그라운드 프로세스 이벤트 핸들링
975
+ const showBackgroundPanelRef = useRef(showBackgroundPanel);
976
+ useEffect(() => {
977
+ showBackgroundPanelRef.current = showBackgroundPanel;
978
+ }, [showBackgroundPanel]);
979
+
980
+ useEffect(() => {
981
+ let manager = null;
982
+ let updateInterval = null;
983
+ let cleanupFn = null;
984
+ let isMounted = true;
985
+
986
+ const setupBackgroundProcessListener = async () => {
987
+ const { getBackgroundProcessManager } = await import('../system/background_process.js');
988
+ if (!isMounted) return;
989
+
990
+ manager = getBackgroundProcessManager();
991
+
992
+ const updateProcessList = () => {
993
+ if (!isMounted) return;
994
+ const processes = manager.list();
995
+ // 실행 중인 프로세스만 표시
996
+ const runningProcesses = processes.filter(p => p.status === 'running');
997
+ setBackgroundProcesses(runningProcesses);
998
+
999
+ // 실행 중인 프로세스가 있으면 패널 자동 표시
1000
+ if (runningProcesses.length > 0 && !showBackgroundPanelRef.current) {
1001
+ setShowBackgroundPanel(true);
1002
+ }
1003
+ // 실행 중인 프로세스가 없으면 패널 숨기기
1004
+ if (runningProcesses.length === 0) {
1005
+ setShowBackgroundPanel(false);
1006
+ setBackgroundPanelFocused(false);
1007
+ }
1008
+ };
1009
+
1010
+ // 이벤트 핸들러
1011
+ const handleStarted = () => {
1012
+ updateProcessList();
1013
+ };
1014
+ const handleClose = () => updateProcessList();
1015
+ const handleKilled = () => updateProcessList();
1016
+
1017
+ manager.on('started', handleStarted);
1018
+ manager.on('close', handleClose);
1019
+ manager.on('killed', handleKilled);
1020
+
1021
+ // 초기 로드
1022
+ updateProcessList();
1023
+
1024
+ // 주기적 업데이트 (실행 시간 표시용)
1025
+ updateInterval = setInterval(updateProcessList, 1000);
1026
+
1027
+ cleanupFn = () => {
1028
+ manager.off('started', handleStarted);
1029
+ manager.off('close', handleClose);
1030
+ manager.off('killed', handleKilled);
1031
+ if (updateInterval) clearInterval(updateInterval);
1032
+ };
1033
+ };
1034
+
1035
+ setupBackgroundProcessListener();
1036
+
1037
+ return () => {
1038
+ isMounted = false;
1039
+ if (cleanupFn) cleanupFn();
1040
+ if (updateInterval) clearInterval(updateInterval);
1041
+ };
1042
+ }, []);
1043
+
1044
+ // 백그라운드 프로세스 kill 핸들러
1045
+ const handleKillBackgroundProcess = useCallback(async (id) => {
1046
+ const { killBackgroundProcess } = await import('../system/background_process.js');
1047
+ killBackgroundProcess(id);
1048
+ }, []);
1049
+
1050
+ // 백그라운드 패널 닫기 핸들러
1051
+ const handleCloseBackgroundPanel = useCallback(() => {
1052
+ setBackgroundPanelFocused(false);
1053
+ }, []);
1054
+
1055
+ // 백그라운드 패널 포커스 토글 핸들러
1056
+ const handleToggleBackgroundPanelFocus = useCallback(() => {
1057
+ if (backgroundProcesses.length > 0 && showBackgroundPanel) {
1058
+ setBackgroundPanelFocused(prev => !prev);
1059
+ }
1060
+ }, [backgroundProcesses.length, showBackgroundPanel]);
1061
+
962
1062
  // 승인 요청 처리
963
1063
  const handleApprovalDecision = useCallback((decision) => {
964
1064
  if (approvalRequest && approvalRequest.resolve) {
@@ -1103,13 +1203,20 @@ export function App({ onSubmit, onClearScreen, onExit, commands = [], model, ver
1103
1203
  !approvalRequest && todos.length > 0 && React.createElement(TodoList, {
1104
1204
  todos: todos
1105
1205
  }),
1206
+ !approvalRequest && showBackgroundPanel && backgroundProcesses.length > 0 && React.createElement(BackgroundProcessList, {
1207
+ processes: backgroundProcesses,
1208
+ isActive: backgroundPanelFocused && !isSessionRunning && !approvalRequest,
1209
+ onKill: handleKillBackgroundProcess,
1210
+ onClose: handleCloseBackgroundPanel,
1211
+ onToggleFocus: handleToggleBackgroundPanelFocus
1212
+ }),
1106
1213
  !approvalRequest && React.createElement(Input, {
1107
1214
  buffer,
1108
1215
  onSubmit: handleSubmit,
1109
1216
  onClearScreen: handleClearScreen,
1110
1217
  onExit: onExit,
1111
1218
  commands,
1112
- focus: true,
1219
+ focus: !backgroundPanelFocused,
1113
1220
  isSessionRunning
1114
1221
  }),
1115
1222
  React.createElement(MemoizedFooter, { model: currentModel, reasoningEffort })
@@ -21,13 +21,19 @@ export function AutocompleteMenu({ suggestions = [], activeIndex = 0 }) {
21
21
  React.createElement(Text, { color: theme.text.secondary, bold: true }, 'Suggestions'),
22
22
  suggestions.map((suggestion, index) => {
23
23
  const isActive = index === activeIndex;
24
+ // 아이콘이 있으면 표시, 없으면 값만 표시
25
+ const icon = suggestion.icon || '';
26
+ const displayText = suggestion.displayValue || suggestion.value;
27
+ const prefix = icon ? `${icon} ` : '';
28
+
24
29
  return React.createElement(Box, { key: index, marginLeft: 1 },
25
30
  React.createElement(Text, {
26
31
  color: isActive ? theme.text.accent : theme.text.primary,
27
32
  bold: isActive
28
33
  },
29
34
  isActive ? '▶ ' : ' ',
30
- suggestion.value,
35
+ prefix,
36
+ displayText,
31
37
  suggestion.description && React.createElement(Text, { color: theme.text.secondary },
32
38
  ' - ' + suggestion.description)
33
39
  )