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
|
@@ -2,9 +2,10 @@ import dotenv from "dotenv";
|
|
|
2
2
|
// 이 파일은 계획을 실제 행동으로 옮기기 위해 어떤 도구를 호출할지 결정합니다.
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { truncateWithOmit } from "../util/text_formatter.js";
|
|
5
|
-
import { createSystemMessage } from "../util/prompt_loader.js";
|
|
5
|
+
import { createSystemMessage, createTodoReminder, createSystemReminder, createTrimmedFileReminder } from "../util/prompt_loader.js";
|
|
6
6
|
import { request, shouldRetryWithTrim, getModelForProvider } from "../system/ai_request.js";
|
|
7
|
-
import {
|
|
7
|
+
import { supportsCaching } from "../config/ai_models.js";
|
|
8
|
+
import { cleanupOrphanOutputs, trimConversation, getTrimmedFileReads, clearTrimmedFileReads } from "../system/conversation_trimmer.js";
|
|
8
9
|
import { runPythonCodeSchema, bashSchema } from "../system/code_executer.js";
|
|
9
10
|
import { readFileSchema, readFileRangeSchema } from "../tools/file_reader.js";
|
|
10
11
|
import { writeFileSchema, editFileRangeSchema, editFileReplaceSchema } from "../tools/code_editor.js";
|
|
@@ -13,9 +14,11 @@ import { responseMessageSchema } from "../tools/response_message.js";
|
|
|
13
14
|
import { todoWriteSchema } from "../tools/todo_write.js";
|
|
14
15
|
import { ripgrepSchema } from "../tools/ripgrep.js";
|
|
15
16
|
import { globSearchSchema } from "../tools/glob.js";
|
|
17
|
+
import { skillSchema, generateSkillListForPrompt } from "../tools/skill_tool.js";
|
|
16
18
|
import { loadSettings } from "../util/config.js";
|
|
17
19
|
import { createDebugLogger } from "../util/debug_log.js";
|
|
18
20
|
import { getCurrentTodos } from "../system/session_memory.js";
|
|
21
|
+
import { loadAgentsMdForPrompt } from "../system/agents_loader.js";
|
|
19
22
|
dotenv.config({ quiet: true });
|
|
20
23
|
|
|
21
24
|
const debugLog = createDebugLogger('orchestrator.log', 'orchestrator');
|
|
@@ -26,42 +29,144 @@ let orchestratorRequestOptions = null;
|
|
|
26
29
|
/**
|
|
27
30
|
* Orchestrator conversation을 초기화하거나 시스템 프롬프트를 업데이트합니다.
|
|
28
31
|
* 매 요청마다 프롬프트 파일을 새로 읽어서 변경사항을 즉시 반영합니다.
|
|
32
|
+
*
|
|
33
|
+
* 로그 분석에서 발견한 전략 적용:
|
|
34
|
+
* 1. 캐시 제어 (cache_control: ephemeral) - Z.AI/GLM 모델의 경우
|
|
35
|
+
* 2. System-Reminder 패턴 - TODO 상태 등을 별도 블록으로 분리
|
|
36
|
+
* 3. 다중 시스템 메시지 구조 - 캐시 효과 극대화
|
|
29
37
|
*/
|
|
30
38
|
async function ensureConversationInitialized() {
|
|
31
|
-
//
|
|
39
|
+
// 현재 모델 확인 (캐시 제어 여부 결정)
|
|
40
|
+
const currentModel = await getModelForProvider();
|
|
41
|
+
const useCache = supportsCaching(currentModel);
|
|
42
|
+
debugLog(`[ensureConversationInitialized] Model: ${currentModel}, Cache enabled: ${useCache}`);
|
|
43
|
+
|
|
44
|
+
// 매번 최신 시스템 프롬프트를 로드 (캐시 제어 옵션 적용)
|
|
32
45
|
const systemMessage = await createSystemMessage("orchestrator.txt", {
|
|
33
46
|
CWD: process.cwd(),
|
|
34
47
|
OS: process.app_custom?.systemInfo?.os || 'unknown'
|
|
48
|
+
}, {
|
|
49
|
+
model: currentModel,
|
|
50
|
+
enableCache: useCache
|
|
35
51
|
});
|
|
36
52
|
|
|
37
53
|
// 현재 todos 가져오기
|
|
38
54
|
const currentTodos = getCurrentTodos();
|
|
39
55
|
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
56
|
+
// 사용 가능한 스킬 목록 생성
|
|
57
|
+
const skillListText = await generateSkillListForPrompt();
|
|
58
|
+
debugLog(`[ensureConversationInitialized] Generated skill list: ${skillListText ? 'has skills' : 'no skills'}`);
|
|
59
|
+
|
|
60
|
+
// AGENTS.md 로드 (프로젝트별 에이전트 지침)
|
|
61
|
+
const agentsMdText = await loadAgentsMdForPrompt();
|
|
62
|
+
debugLog(`[ensureConversationInitialized] AGENTS.md: ${agentsMdText ? `loaded (${agentsMdText.length} chars)` : 'not found'}`);
|
|
63
|
+
|
|
64
|
+
// 시스템 메시지 구성 (캐싱 모델의 경우 다중 블록 구조)
|
|
65
|
+
let systemMessageEntry;
|
|
66
|
+
|
|
67
|
+
if (useCache && Array.isArray(systemMessage.content)) {
|
|
68
|
+
// 캐싱 모델: 다중 블록 구조 사용 (Claude Code 스타일)
|
|
69
|
+
const contentBlocks = [...systemMessage.content];
|
|
70
|
+
|
|
71
|
+
// AGENTS.md를 별도 블록으로 추가 (프로젝트별 지침)
|
|
72
|
+
if (agentsMdText) {
|
|
73
|
+
contentBlocks.push({
|
|
74
|
+
type: "input_text",
|
|
75
|
+
text: agentsMdText,
|
|
76
|
+
cache_control: { type: "ephemeral" } // 프로젝트가 바뀔 때만 변경되므로 캐시
|
|
77
|
+
});
|
|
78
|
+
debugLog(`[ensureConversationInitialized] Added AGENTS.md as separate block (cached)`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 스킬 목록을 별도 블록으로 추가
|
|
82
|
+
if (skillListText) {
|
|
83
|
+
contentBlocks.push({
|
|
84
|
+
type: "input_text",
|
|
85
|
+
text: skillListText
|
|
86
|
+
// 스킬 목록은 자주 변경될 수 있으므로 캐시하지 않음
|
|
87
|
+
});
|
|
88
|
+
debugLog(`[ensureConversationInitialized] Added skill list as separate block`);
|
|
89
|
+
}
|
|
55
90
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
{
|
|
91
|
+
// TODO 리마인더를 별도 블록으로 추가 (캐시 대상 아님)
|
|
92
|
+
if (currentTodos && currentTodos.length > 0) {
|
|
93
|
+
const todoReminder = createTodoReminder(currentTodos);
|
|
94
|
+
contentBlocks.push({
|
|
60
95
|
type: "input_text",
|
|
61
|
-
text:
|
|
96
|
+
text: todoReminder
|
|
97
|
+
// cache_control 없음 - 동적 데이터이므로
|
|
98
|
+
});
|
|
99
|
+
debugLog(`[ensureConversationInitialized] Added ${currentTodos.length} todos as separate block (uncached)`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Trim된 파일 읽기 알림 추가 (파일 경로 알림 기능)
|
|
103
|
+
const trimmedFiles = getTrimmedFileReads();
|
|
104
|
+
if (trimmedFiles.length > 0) {
|
|
105
|
+
const trimmedFileReminder = createTrimmedFileReminder(trimmedFiles);
|
|
106
|
+
if (trimmedFileReminder) {
|
|
107
|
+
contentBlocks.push({
|
|
108
|
+
type: "input_text",
|
|
109
|
+
text: trimmedFileReminder
|
|
110
|
+
// cache_control 없음 - 동적 데이터이므로
|
|
111
|
+
});
|
|
112
|
+
debugLog(`[ensureConversationInitialized] Added trimmed file reminder for ${trimmedFiles.length} files (uncached)`);
|
|
62
113
|
}
|
|
63
|
-
|
|
64
|
-
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
systemMessageEntry = {
|
|
117
|
+
role: "system",
|
|
118
|
+
content: contentBlocks
|
|
119
|
+
};
|
|
120
|
+
} else {
|
|
121
|
+
// 일반 모델: 기존 방식 유지
|
|
122
|
+
let systemMessageText = systemMessage.content;
|
|
123
|
+
|
|
124
|
+
// AGENTS.md 추가 (프로젝트별 지침)
|
|
125
|
+
if (agentsMdText) {
|
|
126
|
+
systemMessageText += agentsMdText;
|
|
127
|
+
debugLog(`[ensureConversationInitialized] Added AGENTS.md to system message`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// 스킬 목록 추가
|
|
131
|
+
if (skillListText) {
|
|
132
|
+
systemMessageText += skillListText;
|
|
133
|
+
debugLog(`[ensureConversationInitialized] Added skill list to system message`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (currentTodos && currentTodos.length > 0) {
|
|
137
|
+
const todosSection = '\n\n## Current Task List (Your TODO tracker)\n\n' +
|
|
138
|
+
'You have created the following task list to track your work. Continue working on these tasks systematically:\n\n' +
|
|
139
|
+
currentTodos.map((todo, index) => {
|
|
140
|
+
const statusIcon = todo.status === 'completed' ? '✓' :
|
|
141
|
+
todo.status === 'in_progress' ? '→' : '○';
|
|
142
|
+
return `${index + 1}. [${statusIcon}] ${todo.content} (${todo.status})`;
|
|
143
|
+
}).join('\n') +
|
|
144
|
+
'\n\n**Remember**: Mark tasks as completed immediately after finishing them. Keep exactly ONE task as in_progress at a time.\n';
|
|
145
|
+
|
|
146
|
+
systemMessageText += todosSection;
|
|
147
|
+
debugLog(`[ensureConversationInitialized] Added ${currentTodos.length} todos to system message`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Trim된 파일 읽기 알림 추가 (파일 경로 알림 기능)
|
|
151
|
+
const trimmedFiles = getTrimmedFileReads();
|
|
152
|
+
if (trimmedFiles.length > 0) {
|
|
153
|
+
const trimmedFileReminder = createTrimmedFileReminder(trimmedFiles);
|
|
154
|
+
if (trimmedFileReminder) {
|
|
155
|
+
systemMessageText += '\n\n' + trimmedFileReminder;
|
|
156
|
+
debugLog(`[ensureConversationInitialized] Added trimmed file reminder for ${trimmedFiles.length} files`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
systemMessageEntry = {
|
|
161
|
+
role: "system",
|
|
162
|
+
content: [
|
|
163
|
+
{
|
|
164
|
+
type: "input_text",
|
|
165
|
+
text: systemMessageText
|
|
166
|
+
}
|
|
167
|
+
]
|
|
168
|
+
};
|
|
169
|
+
}
|
|
65
170
|
|
|
66
171
|
// conversation이 비어있으면 새로 추가
|
|
67
172
|
if (!orchestratorConversation.length) {
|
|
@@ -97,6 +202,7 @@ function appendResponseToConversation(response) {
|
|
|
97
202
|
export function resetOrchestratorConversation() {
|
|
98
203
|
orchestratorConversation.length = 0;
|
|
99
204
|
orchestratorRequestOptions = null;
|
|
205
|
+
clearTrimmedFileReads(); // 세션 초기화 시 trim된 파일 목록도 초기화
|
|
100
206
|
}
|
|
101
207
|
|
|
102
208
|
export function getOrchestratorConversation() {
|
|
@@ -201,11 +307,13 @@ async function dispatchOrchestratorRequest({ toolChoice }) {
|
|
|
201
307
|
const estimatedTokens = Math.ceil(conversationJson.length / 4); // 대략 4 chars = 1 token
|
|
202
308
|
debugLog(`[dispatchOrchestratorRequest] Estimated tokens: ${estimatedTokens} (conversation size: ${conversationJson.length} bytes)`);
|
|
203
309
|
|
|
204
|
-
|
|
310
|
+
const apiStartTime = Date.now();
|
|
311
|
+
debugLog(`[dispatchOrchestratorRequest] >>>>> Sending API request at ${new Date(apiStartTime).toISOString()}...`);
|
|
205
312
|
|
|
206
313
|
try {
|
|
207
314
|
const response = await request(taskName, requestConfig);
|
|
208
|
-
|
|
315
|
+
const apiDuration = Date.now() - apiStartTime;
|
|
316
|
+
debugLog(`[dispatchOrchestratorRequest] <<<<< API request successful in ${apiDuration}ms`);
|
|
209
317
|
debugLog(`[dispatchOrchestratorRequest] Response outputs: ${response?.output?.length || 0}`);
|
|
210
318
|
if (response?.output) {
|
|
211
319
|
const outputTypes = {};
|
|
@@ -219,7 +327,8 @@ async function dispatchOrchestratorRequest({ toolChoice }) {
|
|
|
219
327
|
// consolelog(JSON.stringify(response, null, 2));
|
|
220
328
|
return response;
|
|
221
329
|
} catch (error) {
|
|
222
|
-
|
|
330
|
+
const apiDuration = Date.now() - apiStartTime;
|
|
331
|
+
debugLog(`[dispatchOrchestratorRequest] <<<<< API request FAILED after ${apiDuration}ms: ${error.message}`);
|
|
223
332
|
debugLog(`[dispatchOrchestratorRequest] Error name: ${error.name}, type: ${error?.constructor?.name}`);
|
|
224
333
|
|
|
225
334
|
// AbortError는 즉시 전파 (세션 중단)
|
|
@@ -308,7 +417,8 @@ export async function orchestrateMission({ improvement_points = '', mcpToolSchem
|
|
|
308
417
|
response_message: responseMessageSchema,
|
|
309
418
|
todo_write: todoWriteSchema,
|
|
310
419
|
ripgrep: ripgrepSchema,
|
|
311
|
-
glob_search: globSearchSchema
|
|
420
|
+
glob_search: globSearchSchema,
|
|
421
|
+
invoke_skill: skillSchema // 스킬 호출 도구
|
|
312
422
|
};
|
|
313
423
|
|
|
314
424
|
// Python 관련 도구 (조건부 추가)
|
|
@@ -355,14 +465,40 @@ export async function orchestrateMission({ improvement_points = '', mcpToolSchem
|
|
|
355
465
|
debugLog(`[orchestrateMission] Conversation initialized, current length: ${orchestratorConversation.length}`);
|
|
356
466
|
|
|
357
467
|
debugLog(`[orchestrateMission] Adding user message to conversation: "${improvementPointsText.substring(0, 100)}"`);
|
|
468
|
+
|
|
469
|
+
// 캐싱 모델인 경우 사용자 메시지에도 캐시 제어 적용 (Claude Code 스타일)
|
|
470
|
+
const useCache = supportsCaching(model);
|
|
471
|
+
const userContentBlocks = [];
|
|
472
|
+
|
|
473
|
+
// Auto-generated 메시지의 경우 system-reminder 추가
|
|
474
|
+
if (isAutoGenerated) {
|
|
475
|
+
const autoGenReminder = createSystemReminder(
|
|
476
|
+
'이 메시지는 시스템에서 자동 생성되었습니다. 사용자가 직접 입력한 것이 아닙니다.',
|
|
477
|
+
{ hideFromUser: true }
|
|
478
|
+
);
|
|
479
|
+
userContentBlocks.push({
|
|
480
|
+
type: "input_text",
|
|
481
|
+
text: autoGenReminder
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// 메인 사용자 메시지
|
|
486
|
+
const mainContent = {
|
|
487
|
+
type: "input_text",
|
|
488
|
+
text: improvementPointsText
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
// 캐싱 모델의 경우 사용자 메시지에도 캐시 적용 (반복되는 긴 메시지에 효과적)
|
|
492
|
+
if (useCache && improvementPointsText.length > 500) {
|
|
493
|
+
mainContent.cache_control = { type: "ephemeral" };
|
|
494
|
+
debugLog(`[orchestrateMission] Applied cache control to user message (${improvementPointsText.length} chars)`);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
userContentBlocks.push(mainContent);
|
|
498
|
+
|
|
358
499
|
const userMessage = {
|
|
359
500
|
role: "user",
|
|
360
|
-
content:
|
|
361
|
-
{
|
|
362
|
-
type: "input_text",
|
|
363
|
-
text: improvementPointsText
|
|
364
|
-
}
|
|
365
|
-
]
|
|
501
|
+
content: userContentBlocks
|
|
366
502
|
};
|
|
367
503
|
|
|
368
504
|
// Auto-generated user message인 경우 _internal_only 플래그 추가
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { uiEvents } from '../system/ui_events.js';
|
|
2
|
+
import {
|
|
3
|
+
formatAgentsMdSummary,
|
|
4
|
+
discoverAllAgentsMd
|
|
5
|
+
} from '../system/agents_loader.js';
|
|
6
|
+
import { safeReadFile } from '../util/safe_fs.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* /agents 커맨드 - AGENTS.md 파일 조회
|
|
10
|
+
*
|
|
11
|
+
* 사용법:
|
|
12
|
+
* /agents - 로드된 AGENTS.md 파일 목록 및 요약 표시
|
|
13
|
+
* /agents <path> - 특정 AGENTS.md 파일 전체 내용 표시
|
|
14
|
+
*/
|
|
15
|
+
export default {
|
|
16
|
+
name: 'agents',
|
|
17
|
+
description: 'Show AGENTS.md files and their contents',
|
|
18
|
+
usage: '/agents [file-path]',
|
|
19
|
+
handler: async (args, context) => {
|
|
20
|
+
if (!args || args.length === 0) {
|
|
21
|
+
// AGENTS.md 요약 표시
|
|
22
|
+
const summary = await formatAgentsMdSummary();
|
|
23
|
+
uiEvents.addSystemMessage(summary);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 특정 파일 내용 표시
|
|
28
|
+
const filePath = args[0];
|
|
29
|
+
|
|
30
|
+
// 숫자가 입력된 경우 인덱스로 처리
|
|
31
|
+
const index = parseInt(filePath, 10);
|
|
32
|
+
if (!isNaN(index) && index > 0) {
|
|
33
|
+
const agentsMdFiles = await discoverAllAgentsMd();
|
|
34
|
+
if (index > agentsMdFiles.length) {
|
|
35
|
+
uiEvents.addSystemMessage(`Invalid index: ${index}. Found ${agentsMdFiles.length} AGENTS.md file(s).`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const file = agentsMdFiles[index - 1];
|
|
40
|
+
const info = [
|
|
41
|
+
`AGENTS.md (${file.source})`,
|
|
42
|
+
`Path: ${file.path}`,
|
|
43
|
+
'',
|
|
44
|
+
'--- Content ---',
|
|
45
|
+
'',
|
|
46
|
+
file.content
|
|
47
|
+
].join('\n');
|
|
48
|
+
|
|
49
|
+
uiEvents.addSystemMessage(info);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 경로가 입력된 경우 해당 파일 읽기
|
|
54
|
+
try {
|
|
55
|
+
const content = await safeReadFile(filePath, 'utf8');
|
|
56
|
+
const info = [
|
|
57
|
+
`AGENTS.md`,
|
|
58
|
+
`Path: ${filePath}`,
|
|
59
|
+
'',
|
|
60
|
+
'--- Content ---',
|
|
61
|
+
'',
|
|
62
|
+
content
|
|
63
|
+
].join('\n');
|
|
64
|
+
|
|
65
|
+
uiEvents.addSystemMessage(info);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
uiEvents.addSystemMessage(`Failed to read file: ${filePath}\n${error.message}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
};
|
package/src/commands/apikey.js
CHANGED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { listBackgroundProcesses, killBackgroundProcess, getBackgroundProcess } from '../system/background_process.js';
|
|
2
|
+
import { uiEvents } from '../system/ui_events.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* /bg 커맨드 - 백그라운드 프로세스 관리
|
|
6
|
+
*
|
|
7
|
+
* 사용법:
|
|
8
|
+
* /bg - 프로세스 목록 표시
|
|
9
|
+
* /bg list - 프로세스 목록 표시
|
|
10
|
+
* /bg kill <id> - 프로세스 종료
|
|
11
|
+
* /bg killall - 모든 실행 중인 프로세스 종료
|
|
12
|
+
*/
|
|
13
|
+
export default {
|
|
14
|
+
name: 'bg',
|
|
15
|
+
description: 'Manage background processes',
|
|
16
|
+
usage: '/bg [list|kill <id>|killall]',
|
|
17
|
+
handler: async (args, context) => {
|
|
18
|
+
const subcommand = args[0]?.toLowerCase() || 'list';
|
|
19
|
+
|
|
20
|
+
switch (subcommand) {
|
|
21
|
+
case 'list':
|
|
22
|
+
case 'ls': {
|
|
23
|
+
const processes = listBackgroundProcesses({ running: true });
|
|
24
|
+
if (processes.length === 0) {
|
|
25
|
+
uiEvents.addSystemMessage('No running background processes');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const lines = ['Background Processes:'];
|
|
30
|
+
for (const proc of processes) {
|
|
31
|
+
const statusIcon = proc.status === 'running' ? '●' :
|
|
32
|
+
proc.status === 'completed' ? '✓' :
|
|
33
|
+
proc.status === 'failed' ? '✗' : '⊘';
|
|
34
|
+
const duration = formatDuration(proc.startedAt, proc.endedAt);
|
|
35
|
+
lines.push(` ${statusIcon} [${proc.id}] pid:${proc.pid} ${duration} - ${proc.command.substring(0, 50)}${proc.command.length > 50 ? '...' : ''}`);
|
|
36
|
+
}
|
|
37
|
+
uiEvents.addSystemMessage(lines.join('\n'));
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
case 'kill': {
|
|
42
|
+
const id = args[1];
|
|
43
|
+
if (!id) {
|
|
44
|
+
uiEvents.addErrorMessage('Usage: /bg kill <process_id>');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const proc = getBackgroundProcess(id);
|
|
49
|
+
if (!proc) {
|
|
50
|
+
uiEvents.addErrorMessage(`Process not found: ${id}`);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (proc.status !== 'running') {
|
|
55
|
+
uiEvents.addSystemMessage(`Process ${id} is already ${proc.status}`);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const killed = killBackgroundProcess(id);
|
|
60
|
+
if (killed) {
|
|
61
|
+
uiEvents.addSystemMessage(`Killed process: ${id} (pid: ${proc.pid})`);
|
|
62
|
+
} else {
|
|
63
|
+
uiEvents.addErrorMessage(`Failed to kill process: ${id}`);
|
|
64
|
+
}
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
case 'killall': {
|
|
69
|
+
const processes = listBackgroundProcesses({ running: true });
|
|
70
|
+
if (processes.length === 0) {
|
|
71
|
+
uiEvents.addSystemMessage('No running background processes');
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
let killedCount = 0;
|
|
76
|
+
for (const proc of processes) {
|
|
77
|
+
if (killBackgroundProcess(proc.id)) {
|
|
78
|
+
killedCount++;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
uiEvents.addSystemMessage(`Killed ${killedCount} background process(es)`);
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
case 'show': {
|
|
86
|
+
const id = args[1];
|
|
87
|
+
if (!id) {
|
|
88
|
+
uiEvents.addErrorMessage('Usage: /bg show <process_id>');
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const proc = getBackgroundProcess(id);
|
|
93
|
+
if (!proc) {
|
|
94
|
+
uiEvents.addErrorMessage(`Process not found: ${id}`);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const lines = [
|
|
99
|
+
`Process: ${proc.id}`,
|
|
100
|
+
` PID: ${proc.pid}`,
|
|
101
|
+
` Status: ${proc.status}`,
|
|
102
|
+
` Command: ${proc.command}`,
|
|
103
|
+
` Started: ${proc.startedAt.toISOString()}`,
|
|
104
|
+
proc.endedAt ? ` Ended: ${proc.endedAt.toISOString()}` : null,
|
|
105
|
+
proc.exitCode !== null ? ` Exit Code: ${proc.exitCode}` : null,
|
|
106
|
+
proc.stdout ? ` Stdout (last 200 chars): ${proc.stdout.slice(-200)}` : null,
|
|
107
|
+
proc.stderr ? ` Stderr (last 200 chars): ${proc.stderr.slice(-200)}` : null,
|
|
108
|
+
].filter(Boolean);
|
|
109
|
+
|
|
110
|
+
uiEvents.addSystemMessage(lines.join('\n'));
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
default:
|
|
115
|
+
uiEvents.addErrorMessage(`Unknown subcommand: ${subcommand}\nUsage: /bg [list|kill <id>|killall|show <id>]`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
function formatDuration(startedAt, endedAt) {
|
|
121
|
+
const end = endedAt ? new Date(endedAt) : new Date();
|
|
122
|
+
const start = new Date(startedAt);
|
|
123
|
+
const diffMs = end - start;
|
|
124
|
+
|
|
125
|
+
if (diffMs < 1000) return `${diffMs}ms`;
|
|
126
|
+
if (diffMs < 60000) return `${Math.round(diffMs / 1000)}s`;
|
|
127
|
+
if (diffMs < 3600000) return `${Math.round(diffMs / 60000)}m`;
|
|
128
|
+
return `${Math.round(diffMs / 3600000)}h`;
|
|
129
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { uiEvents } from '../system/ui_events.js';
|
|
2
|
+
import {
|
|
3
|
+
formatCustomCommandList,
|
|
4
|
+
findCustomCommandByName
|
|
5
|
+
} from '../system/custom_command_loader.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* /commands 커맨드 - 커스텀 커맨드 목록 조회 및 정보 확인
|
|
9
|
+
*
|
|
10
|
+
* 사용법:
|
|
11
|
+
* /commands - 모든 커스텀 커맨드 목록 표시
|
|
12
|
+
* /commands <name> - 특정 커맨드 상세 정보 표시
|
|
13
|
+
*/
|
|
14
|
+
export default {
|
|
15
|
+
name: 'commands',
|
|
16
|
+
description: 'List custom commands or show command details',
|
|
17
|
+
usage: '/commands [command-name]',
|
|
18
|
+
handler: async (args, context) => {
|
|
19
|
+
if (!args || args.length === 0) {
|
|
20
|
+
// 커맨드 목록 표시
|
|
21
|
+
const commandList = await formatCustomCommandList();
|
|
22
|
+
uiEvents.addSystemMessage(commandList);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// 특정 커맨드 정보 표시
|
|
27
|
+
const commandName = args[0];
|
|
28
|
+
const command = await findCustomCommandByName(commandName);
|
|
29
|
+
|
|
30
|
+
if (!command) {
|
|
31
|
+
uiEvents.addSystemMessage(`Custom command not found: ${commandName}\n\nUse /commands to see available commands.`);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const info = [
|
|
36
|
+
`Command: ${command.name}`,
|
|
37
|
+
`Source: ${command.source}`,
|
|
38
|
+
`Path: ${command.path}`,
|
|
39
|
+
command.argumentHint ? `Argument Hint: ${command.argumentHint}` : null,
|
|
40
|
+
command.disableModelInvocation ? 'Model Invocation: Disabled (manual only)' : null,
|
|
41
|
+
'',
|
|
42
|
+
'Frontmatter:',
|
|
43
|
+
JSON.stringify(command.frontmatter, null, 2),
|
|
44
|
+
'',
|
|
45
|
+
'Content Preview:',
|
|
46
|
+
command.content.substring(0, 500) + (command.content.length > 500 ? '...' : '')
|
|
47
|
+
].filter(Boolean).join('\n');
|
|
48
|
+
|
|
49
|
+
uiEvents.addSystemMessage(info);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { uiEvents } from '../system/ui_events.js';
|
|
2
|
+
import { loadSettings, saveSettings, SETTINGS_FILE } from '../util/config.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* /debug 커맨드 - API 요청/응답 내용 표시 설정
|
|
6
|
+
*
|
|
7
|
+
* 사용법:
|
|
8
|
+
* /debug - 현재 설정 상태 표시
|
|
9
|
+
* /debug on - API payload 표시 켜기
|
|
10
|
+
* /debug off - API payload 표시 끄기
|
|
11
|
+
*/
|
|
12
|
+
export default {
|
|
13
|
+
name: 'debug',
|
|
14
|
+
description: 'Toggle API request/response payload display',
|
|
15
|
+
usage: '/debug [on|off]',
|
|
16
|
+
handler: async (args, context) => {
|
|
17
|
+
const settings = await loadSettings();
|
|
18
|
+
const currentValue = settings?.SHOW_API_PAYLOAD === true;
|
|
19
|
+
|
|
20
|
+
if (!args || args.length === 0) {
|
|
21
|
+
// 현재 상태 표시
|
|
22
|
+
const status = currentValue ? 'ON (표시됨)' : 'OFF (숨김)';
|
|
23
|
+
const message = [
|
|
24
|
+
`API Payload Display: ${status}`,
|
|
25
|
+
'',
|
|
26
|
+
'Usage:',
|
|
27
|
+
' /debug - Show current status',
|
|
28
|
+
' /debug on - Enable API payload display',
|
|
29
|
+
' /debug off - Disable API payload display',
|
|
30
|
+
'',
|
|
31
|
+
`Settings file: ${SETTINGS_FILE}`
|
|
32
|
+
].join('\n');
|
|
33
|
+
|
|
34
|
+
uiEvents.addSystemMessage(message);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const action = args[0]?.toLowerCase();
|
|
39
|
+
|
|
40
|
+
if (action === 'on' || action === 'true' || action === '1') {
|
|
41
|
+
settings.SHOW_API_PAYLOAD = true;
|
|
42
|
+
await saveSettings(settings);
|
|
43
|
+
uiEvents.addSystemMessage('API Payload Display: ON\n이제 AI 요청/응답 내용이 화면에 표시됩니다.');
|
|
44
|
+
} else if (action === 'off' || action === 'false' || action === '0') {
|
|
45
|
+
settings.SHOW_API_PAYLOAD = false;
|
|
46
|
+
await saveSettings(settings);
|
|
47
|
+
uiEvents.addSystemMessage('API Payload Display: OFF\nAI 요청/응답 내용이 화면에 표시되지 않습니다.');
|
|
48
|
+
} else {
|
|
49
|
+
uiEvents.addSystemMessage(`Unknown option: ${action}\nUsage: /debug [on|off]`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
};
|
package/src/commands/help.js
CHANGED
|
@@ -2,6 +2,8 @@ import React from 'react';
|
|
|
2
2
|
import { uiEvents } from '../system/ui_events.js';
|
|
3
3
|
import { renderInkComponent } from '../frontend/utils/renderInkComponent.js';
|
|
4
4
|
import { HelpView } from '../frontend/components/HelpView.js';
|
|
5
|
+
import { discoverAllCustomCommands } from '../system/custom_command_loader.js';
|
|
6
|
+
import { discoverAllSkills } from '../system/skill_loader.js';
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* /help 커맨드 - 사용 가능한 커맨드 목록 표시
|
|
@@ -15,8 +17,16 @@ export default {
|
|
|
15
17
|
|
|
16
18
|
const commands = commandRegistry.getCommands();
|
|
17
19
|
|
|
20
|
+
// 커스텀 커맨드 로드
|
|
21
|
+
const customCommands = await discoverAllCustomCommands();
|
|
22
|
+
|
|
23
|
+
// 스킬 로드
|
|
24
|
+
const skills = await discoverAllSkills();
|
|
25
|
+
|
|
18
26
|
const component = React.createElement(HelpView, {
|
|
19
|
-
commands
|
|
27
|
+
commands,
|
|
28
|
+
customCommands,
|
|
29
|
+
skills
|
|
20
30
|
});
|
|
21
31
|
|
|
22
32
|
const output = await renderInkComponent(component);
|