aiexecode 1.0.94 → 1.0.96
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.
- package/README.md +210 -87
- package/index.js +33 -1
- package/package.json +3 -3
- 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/src/LLMClient/client.js +392 -16
- package/src/LLMClient/converters/responses-to-claude.js +67 -18
- package/src/LLMClient/converters/responses-to-zai.js +608 -0
- package/src/LLMClient/errors.js +18 -4
- package/src/LLMClient/index.js +5 -0
- package/src/ai_based/completion_judge.js +35 -4
- package/src/ai_based/orchestrator.js +146 -35
- package/src/commands/agents.js +70 -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 +43 -7
- package/src/commands/skills.js +46 -0
- package/src/config/ai_models.js +96 -5
- package/src/config/constants.js +71 -0
- package/src/frontend/components/HelpView.js +106 -2
- package/src/frontend/components/SetupWizard.js +53 -8
- package/src/frontend/utils/toolUIFormatter.js +261 -0
- package/src/system/agents_loader.js +289 -0
- package/src/system/ai_request.js +147 -9
- package/src/system/command_parser.js +33 -3
- package/src/system/conversation_state.js +265 -0
- package/src/system/custom_command_loader.js +386 -0
- package/src/system/session.js +59 -35
- package/src/system/skill_loader.js +318 -0
- package/src/system/tool_approval.js +10 -0
- package/src/tools/file_reader.js +49 -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 +38 -2
- package/src/util/config_migration.js +174 -0
- package/src/util/path_validator.js +178 -0
- package/src/util/prompt_loader.js +68 -1
- package/src/util/safe_fs.js +43 -3
- package/payload_viewer/out/_next/static/chunks/ecd2072ebf41611f.css +0 -3
- /package/payload_viewer/out/_next/static/{wkEKh6i9XPSyP6rjDRvHn → lHmNygVpv4N1VR0LdnwkJ}/_buildManifest.js +0 -0
- /package/payload_viewer/out/_next/static/{wkEKh6i9XPSyP6rjDRvHn → lHmNygVpv4N1VR0LdnwkJ}/_clientMiddlewareManifest.json +0 -0
- /package/payload_viewer/out/_next/static/{wkEKh6i9XPSyP6rjDRvHn → lHmNygVpv4N1VR0LdnwkJ}/_ssgManifest.js +0 -0
|
@@ -182,15 +182,18 @@ async function dispatchCompletionJudgeRequest(options) {
|
|
|
182
182
|
const estimatedTokens = Math.ceil(conversationJson.length / 4);
|
|
183
183
|
debugLog(`[dispatchCompletionJudgeRequest] Estimated tokens: ${estimatedTokens} (conversation size: ${conversationJson.length} bytes)`);
|
|
184
184
|
|
|
185
|
-
|
|
185
|
+
const apiStartTime = Date.now();
|
|
186
|
+
debugLog(`[dispatchCompletionJudgeRequest] >>>>> Sending API request at ${new Date(apiStartTime).toISOString()}...`);
|
|
186
187
|
try {
|
|
187
188
|
const response = await request(taskName, requestPayload);
|
|
188
|
-
|
|
189
|
+
const apiDuration = Date.now() - apiStartTime;
|
|
190
|
+
debugLog(`[dispatchCompletionJudgeRequest] <<<<< API request successful in ${apiDuration}ms`);
|
|
189
191
|
debugLog(`[dispatchCompletionJudgeRequest] Response output_text length: ${response?.output_text?.length || 0}`);
|
|
190
192
|
debugLog(`[dispatchCompletionJudgeRequest] END - success`);
|
|
191
193
|
return response;
|
|
192
194
|
} catch (error) {
|
|
193
|
-
|
|
195
|
+
const apiDuration = Date.now() - apiStartTime;
|
|
196
|
+
debugLog(`[dispatchCompletionJudgeRequest] <<<<< API request FAILED after ${apiDuration}ms: ${error.message}`);
|
|
194
197
|
debugLog(`[dispatchCompletionJudgeRequest] Error name: ${error.name}, type: ${error?.constructor?.name}`);
|
|
195
198
|
|
|
196
199
|
// AbortError는 즉시 전파 (세션 중단)
|
|
@@ -288,7 +291,34 @@ export async function judgeMissionCompletion(templateVars = {}) {
|
|
|
288
291
|
resetCompletionJudgeConversation();
|
|
289
292
|
|
|
290
293
|
try {
|
|
291
|
-
|
|
294
|
+
let outputText = response.output_text || '';
|
|
295
|
+
let judgment = null;
|
|
296
|
+
|
|
297
|
+
// 1차 시도: 전체 텍스트를 JSON으로 파싱
|
|
298
|
+
try {
|
|
299
|
+
judgment = JSON.parse(outputText);
|
|
300
|
+
debugLog(`[judgeMissionCompletion] Parsed as pure JSON`);
|
|
301
|
+
} catch (e) {
|
|
302
|
+
// 2차 시도: 텍스트 끝에 붙은 JSON 추출 (GLM 모델 대응)
|
|
303
|
+
// 패턴: ...텍스트...{"should_complete":true,"whatUserShouldSay":"..."}
|
|
304
|
+
const jsonMatch = outputText.match(/\{[^{}]*"should_complete"\s*:\s*(true|false)[^{}]*\}/);
|
|
305
|
+
if (jsonMatch) {
|
|
306
|
+
debugLog(`[judgeMissionCompletion] Extracted JSON from text: ${jsonMatch[0]}`);
|
|
307
|
+
judgment = JSON.parse(jsonMatch[0]);
|
|
308
|
+
} else {
|
|
309
|
+
// 3차 시도: 마지막 { } 블록 추출
|
|
310
|
+
const lastBraceStart = outputText.lastIndexOf('{');
|
|
311
|
+
const lastBraceEnd = outputText.lastIndexOf('}');
|
|
312
|
+
if (lastBraceStart !== -1 && lastBraceEnd > lastBraceStart) {
|
|
313
|
+
const jsonCandidate = outputText.substring(lastBraceStart, lastBraceEnd + 1);
|
|
314
|
+
debugLog(`[judgeMissionCompletion] Trying last JSON block: ${jsonCandidate}`);
|
|
315
|
+
judgment = JSON.parse(jsonCandidate);
|
|
316
|
+
} else {
|
|
317
|
+
throw new Error('No JSON found in output');
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
292
322
|
debugLog(`[judgeMissionCompletion] Parsed judgment successfully`);
|
|
293
323
|
debugLog(`[judgeMissionCompletion] should_complete: ${judgment.should_complete}`);
|
|
294
324
|
debugLog(`[judgeMissionCompletion] whatUserShouldSay: ${judgment.whatUserShouldSay}`);
|
|
@@ -308,6 +338,7 @@ export async function judgeMissionCompletion(templateVars = {}) {
|
|
|
308
338
|
} catch (parseError) {
|
|
309
339
|
debugLog(`[judgeMissionCompletion] Parse error: ${parseError.message}`);
|
|
310
340
|
debugLog(`[judgeMissionCompletion] Failed to parse output_text as JSON`);
|
|
341
|
+
debugLog(`[judgeMissionCompletion] Raw output_text: ${response.output_text}`);
|
|
311
342
|
throw new Error('Completion judge response did not include valid JSON output_text.');
|
|
312
343
|
}
|
|
313
344
|
|
|
@@ -2,8 +2,9 @@ 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 } from "../util/prompt_loader.js";
|
|
6
6
|
import { request, shouldRetryWithTrim, getModelForProvider } from "../system/ai_request.js";
|
|
7
|
+
import { supportsCaching } from "../config/ai_models.js";
|
|
7
8
|
import { cleanupOrphanOutputs, trimConversation } 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";
|
|
@@ -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,120 @@ 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];
|
|
55
70
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
{
|
|
71
|
+
// AGENTS.md를 별도 블록으로 추가 (프로젝트별 지침)
|
|
72
|
+
if (agentsMdText) {
|
|
73
|
+
contentBlocks.push({
|
|
60
74
|
type: "input_text",
|
|
61
|
-
text:
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
+
}
|
|
90
|
+
|
|
91
|
+
// TODO 리마인더를 별도 블록으로 추가 (캐시 대상 아님)
|
|
92
|
+
if (currentTodos && currentTodos.length > 0) {
|
|
93
|
+
const todoReminder = createTodoReminder(currentTodos);
|
|
94
|
+
contentBlocks.push({
|
|
95
|
+
type: "input_text",
|
|
96
|
+
text: todoReminder
|
|
97
|
+
// cache_control 없음 - 동적 데이터이므로
|
|
98
|
+
});
|
|
99
|
+
debugLog(`[ensureConversationInitialized] Added ${currentTodos.length} todos as separate block (uncached)`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
systemMessageEntry = {
|
|
103
|
+
role: "system",
|
|
104
|
+
content: contentBlocks
|
|
105
|
+
};
|
|
106
|
+
} else {
|
|
107
|
+
// 일반 모델: 기존 방식 유지
|
|
108
|
+
let systemMessageText = systemMessage.content;
|
|
109
|
+
|
|
110
|
+
// AGENTS.md 추가 (프로젝트별 지침)
|
|
111
|
+
if (agentsMdText) {
|
|
112
|
+
systemMessageText += agentsMdText;
|
|
113
|
+
debugLog(`[ensureConversationInitialized] Added AGENTS.md to system message`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 스킬 목록 추가
|
|
117
|
+
if (skillListText) {
|
|
118
|
+
systemMessageText += skillListText;
|
|
119
|
+
debugLog(`[ensureConversationInitialized] Added skill list to system message`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (currentTodos && currentTodos.length > 0) {
|
|
123
|
+
const todosSection = '\n\n## Current Task List (Your TODO tracker)\n\n' +
|
|
124
|
+
'You have created the following task list to track your work. Continue working on these tasks systematically:\n\n' +
|
|
125
|
+
currentTodos.map((todo, index) => {
|
|
126
|
+
const statusIcon = todo.status === 'completed' ? '✓' :
|
|
127
|
+
todo.status === 'in_progress' ? '→' : '○';
|
|
128
|
+
return `${index + 1}. [${statusIcon}] ${todo.content} (${todo.status})`;
|
|
129
|
+
}).join('\n') +
|
|
130
|
+
'\n\n**Remember**: Mark tasks as completed immediately after finishing them. Keep exactly ONE task as in_progress at a time.\n';
|
|
131
|
+
|
|
132
|
+
systemMessageText += todosSection;
|
|
133
|
+
debugLog(`[ensureConversationInitialized] Added ${currentTodos.length} todos to system message`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
systemMessageEntry = {
|
|
137
|
+
role: "system",
|
|
138
|
+
content: [
|
|
139
|
+
{
|
|
140
|
+
type: "input_text",
|
|
141
|
+
text: systemMessageText
|
|
142
|
+
}
|
|
143
|
+
]
|
|
144
|
+
};
|
|
145
|
+
}
|
|
65
146
|
|
|
66
147
|
// conversation이 비어있으면 새로 추가
|
|
67
148
|
if (!orchestratorConversation.length) {
|
|
@@ -201,11 +282,13 @@ async function dispatchOrchestratorRequest({ toolChoice }) {
|
|
|
201
282
|
const estimatedTokens = Math.ceil(conversationJson.length / 4); // 대략 4 chars = 1 token
|
|
202
283
|
debugLog(`[dispatchOrchestratorRequest] Estimated tokens: ${estimatedTokens} (conversation size: ${conversationJson.length} bytes)`);
|
|
203
284
|
|
|
204
|
-
|
|
285
|
+
const apiStartTime = Date.now();
|
|
286
|
+
debugLog(`[dispatchOrchestratorRequest] >>>>> Sending API request at ${new Date(apiStartTime).toISOString()}...`);
|
|
205
287
|
|
|
206
288
|
try {
|
|
207
289
|
const response = await request(taskName, requestConfig);
|
|
208
|
-
|
|
290
|
+
const apiDuration = Date.now() - apiStartTime;
|
|
291
|
+
debugLog(`[dispatchOrchestratorRequest] <<<<< API request successful in ${apiDuration}ms`);
|
|
209
292
|
debugLog(`[dispatchOrchestratorRequest] Response outputs: ${response?.output?.length || 0}`);
|
|
210
293
|
if (response?.output) {
|
|
211
294
|
const outputTypes = {};
|
|
@@ -219,7 +302,8 @@ async function dispatchOrchestratorRequest({ toolChoice }) {
|
|
|
219
302
|
// consolelog(JSON.stringify(response, null, 2));
|
|
220
303
|
return response;
|
|
221
304
|
} catch (error) {
|
|
222
|
-
|
|
305
|
+
const apiDuration = Date.now() - apiStartTime;
|
|
306
|
+
debugLog(`[dispatchOrchestratorRequest] <<<<< API request FAILED after ${apiDuration}ms: ${error.message}`);
|
|
223
307
|
debugLog(`[dispatchOrchestratorRequest] Error name: ${error.name}, type: ${error?.constructor?.name}`);
|
|
224
308
|
|
|
225
309
|
// AbortError는 즉시 전파 (세션 중단)
|
|
@@ -308,7 +392,8 @@ export async function orchestrateMission({ improvement_points = '', mcpToolSchem
|
|
|
308
392
|
response_message: responseMessageSchema,
|
|
309
393
|
todo_write: todoWriteSchema,
|
|
310
394
|
ripgrep: ripgrepSchema,
|
|
311
|
-
glob_search: globSearchSchema
|
|
395
|
+
glob_search: globSearchSchema,
|
|
396
|
+
invoke_skill: skillSchema // 스킬 호출 도구
|
|
312
397
|
};
|
|
313
398
|
|
|
314
399
|
// Python 관련 도구 (조건부 추가)
|
|
@@ -355,14 +440,40 @@ export async function orchestrateMission({ improvement_points = '', mcpToolSchem
|
|
|
355
440
|
debugLog(`[orchestrateMission] Conversation initialized, current length: ${orchestratorConversation.length}`);
|
|
356
441
|
|
|
357
442
|
debugLog(`[orchestrateMission] Adding user message to conversation: "${improvementPointsText.substring(0, 100)}"`);
|
|
443
|
+
|
|
444
|
+
// 캐싱 모델인 경우 사용자 메시지에도 캐시 제어 적용 (Claude Code 스타일)
|
|
445
|
+
const useCache = supportsCaching(model);
|
|
446
|
+
const userContentBlocks = [];
|
|
447
|
+
|
|
448
|
+
// Auto-generated 메시지의 경우 system-reminder 추가
|
|
449
|
+
if (isAutoGenerated) {
|
|
450
|
+
const autoGenReminder = createSystemReminder(
|
|
451
|
+
'이 메시지는 시스템에서 자동 생성되었습니다. 사용자가 직접 입력한 것이 아닙니다.',
|
|
452
|
+
{ hideFromUser: true }
|
|
453
|
+
);
|
|
454
|
+
userContentBlocks.push({
|
|
455
|
+
type: "input_text",
|
|
456
|
+
text: autoGenReminder
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// 메인 사용자 메시지
|
|
461
|
+
const mainContent = {
|
|
462
|
+
type: "input_text",
|
|
463
|
+
text: improvementPointsText
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
// 캐싱 모델의 경우 사용자 메시지에도 캐시 적용 (반복되는 긴 메시지에 효과적)
|
|
467
|
+
if (useCache && improvementPointsText.length > 500) {
|
|
468
|
+
mainContent.cache_control = { type: "ephemeral" };
|
|
469
|
+
debugLog(`[orchestrateMission] Applied cache control to user message (${improvementPointsText.length} chars)`);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
userContentBlocks.push(mainContent);
|
|
473
|
+
|
|
358
474
|
const userMessage = {
|
|
359
475
|
role: "user",
|
|
360
|
-
content:
|
|
361
|
-
{
|
|
362
|
-
type: "input_text",
|
|
363
|
-
text: improvementPointsText
|
|
364
|
-
}
|
|
365
|
-
]
|
|
476
|
+
content: userContentBlocks
|
|
366
477
|
};
|
|
367
478
|
|
|
368
479
|
// 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
|
+
};
|
|
@@ -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);
|
package/src/commands/model.js
CHANGED
|
@@ -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
|
-
//
|
|
20
|
-
const providers =
|
|
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
|
|
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,23 @@ 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
|
+
'openai': 'Set your OpenAI API key with: `/apikey sk-proj-...`',
|
|
147
|
+
'zai': 'Set your Z.AI API key with: `/apikey <32hex>.<16chars>`'
|
|
148
|
+
};
|
|
119
149
|
warning = {
|
|
120
150
|
message: 'API key is not configured.',
|
|
121
|
-
hint: 'Set your API key with: `/apikey
|
|
151
|
+
hint: apiKeyHints[provider] || 'Set your API key with: `/apikey <your-key>`'
|
|
152
|
+
};
|
|
153
|
+
} else if (provider === 'zai') {
|
|
154
|
+
// Z.AI 모델 선택 시 추가 안내
|
|
155
|
+
warning = {
|
|
156
|
+
message: `BASE_URL automatically set to: ${defaultBaseUrl}`,
|
|
157
|
+
hint: 'Ensure your API key is valid for Z.AI service.'
|
|
122
158
|
};
|
|
123
159
|
}
|
|
124
160
|
|
|
@@ -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
|
+
};
|