aiexecode 1.0.157
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/LICENSE +68 -0
- package/README.md +347 -0
- package/config_template/mcp_config.json +3 -0
- package/config_template/package_name_store.json +5 -0
- package/config_template/settings.json +5 -0
- package/index.js +879 -0
- package/mcp-agent-lib/example/01-basic-usage.js +82 -0
- package/mcp-agent-lib/example/02-quick-start.js +52 -0
- package/mcp-agent-lib/example/03-http-server.js +76 -0
- package/mcp-agent-lib/example/04-multiple-servers.js +117 -0
- package/mcp-agent-lib/example/05-error-handling.js +116 -0
- package/mcp-agent-lib/example/06-resources-and-prompts.js +174 -0
- package/mcp-agent-lib/example/07-advanced-configuration.js +191 -0
- package/mcp-agent-lib/example/08-real-world-chatbot.js +331 -0
- package/mcp-agent-lib/example/README.md +346 -0
- package/mcp-agent-lib/index.js +19 -0
- package/mcp-agent-lib/init.sh +3 -0
- package/mcp-agent-lib/package-lock.json +1216 -0
- package/mcp-agent-lib/package.json +53 -0
- package/mcp-agent-lib/sampleFastMCPClient/client.py +25 -0
- package/mcp-agent-lib/sampleFastMCPClient/run.sh +3 -0
- package/mcp-agent-lib/sampleFastMCPServer/run.sh +3 -0
- package/mcp-agent-lib/sampleFastMCPServer/server.py +12 -0
- package/mcp-agent-lib/sampleFastMCPServerElicitationRequest/run.sh +3 -0
- package/mcp-agent-lib/sampleFastMCPServerElicitationRequest/server.py +43 -0
- package/mcp-agent-lib/sampleFastMCPServerRootsRequest/server.py +63 -0
- package/mcp-agent-lib/sampleMCPHost/index.js +386 -0
- package/mcp-agent-lib/sampleMCPHost/mcp_config.json +24 -0
- package/mcp-agent-lib/sampleMCPHostFeatures/elicitation.js +151 -0
- package/mcp-agent-lib/sampleMCPHostFeatures/index.js +166 -0
- package/mcp-agent-lib/sampleMCPHostFeatures/roots.js +197 -0
- package/mcp-agent-lib/src/mcp_client.js +1860 -0
- package/mcp-agent-lib/src/mcp_message_logger.js +517 -0
- package/package.json +72 -0
- package/payload_viewer/out/404/index.html +1 -0
- package/payload_viewer/out/404.html +1 -0
- package/payload_viewer/out/_next/static/chunks/060f9a97930f3d04.js +1 -0
- package/payload_viewer/out/_next/static/chunks/103c802c8f4a5ea1.js +1 -0
- package/payload_viewer/out/_next/static/chunks/16474fd6c6910c45.js +1 -0
- package/payload_viewer/out/_next/static/chunks/17722e3ac4e00587.js +1 -0
- package/payload_viewer/out/_next/static/chunks/305b077a9873cf54.js +1 -0
- package/payload_viewer/out/_next/static/chunks/4c1d05c6741c2bdd.js +5 -0
- package/payload_viewer/out/_next/static/chunks/538cc02e54714b23.js +1 -0
- package/payload_viewer/out/_next/static/chunks/6251fa5907d2b226.js +5 -0
- package/payload_viewer/out/_next/static/chunks/a6dad97d9634a72d.js +1 -0
- package/payload_viewer/out/_next/static/chunks/b6c0459f3789d25c.js +1 -0
- package/payload_viewer/out/_next/static/chunks/b75131b58f8ca46a.css +3 -0
- package/payload_viewer/out/_next/static/chunks/bd2dcf98c9b362f6.js +1 -0
- package/payload_viewer/out/_next/static/chunks/c8a542ae21335479.js +1 -0
- package/payload_viewer/out/_next/static/chunks/cdd12d5c1a5a6064.js +1 -0
- package/payload_viewer/out/_next/static/chunks/e411019f55d87c42.js +1 -0
- package/payload_viewer/out/_next/static/chunks/e60ef129113f6e24.js +1 -0
- package/payload_viewer/out/_next/static/chunks/f1ac9047ac4a3fde.js +1 -0
- package/payload_viewer/out/_next/static/chunks/turbopack-0ac29803ce3c3c7a.js +3 -0
- package/payload_viewer/out/_next/static/chunks/turbopack-89db4c64206a73e4.js +3 -0
- package/payload_viewer/out/_next/static/chunks/turbopack-a5b8235fa59d7119.js +3 -0
- package/payload_viewer/out/_next/static/media/4fa387ec64143e14-s.c1fdd6c2.woff2 +0 -0
- package/payload_viewer/out/_next/static/media/7178b3e590c64307-s.b97b3418.woff2 +0 -0
- package/payload_viewer/out/_next/static/media/797e433ab948586e-s.p.dbea232f.woff2 +0 -0
- package/payload_viewer/out/_next/static/media/8a480f0b521d4e75-s.8e0177b5.woff2 +0 -0
- package/payload_viewer/out/_next/static/media/bbc41e54d2fcbd21-s.799d8ef8.woff2 +0 -0
- package/payload_viewer/out/_next/static/media/caa3a2e1cccd8315-s.p.853070df.woff2 +0 -0
- package/payload_viewer/out/_next/static/media/favicon.0b3bf435.ico +0 -0
- package/payload_viewer/out/_next/static/uqQYtKpKV7kCSkUbLgdfJ/_buildManifest.js +14 -0
- package/payload_viewer/out/_next/static/uqQYtKpKV7kCSkUbLgdfJ/_clientMiddlewareManifest.json +1 -0
- package/payload_viewer/out/_next/static/uqQYtKpKV7kCSkUbLgdfJ/_ssgManifest.js +1 -0
- package/payload_viewer/out/favicon.ico +0 -0
- package/payload_viewer/out/file.svg +1 -0
- package/payload_viewer/out/globe.svg +1 -0
- package/payload_viewer/out/index.html +1 -0
- package/payload_viewer/out/index.txt +23 -0
- package/payload_viewer/out/next.svg +1 -0
- package/payload_viewer/out/vercel.svg +1 -0
- package/payload_viewer/out/window.svg +1 -0
- package/payload_viewer/web_server.js +861 -0
- package/prompts/completion_judge.txt +128 -0
- package/prompts/orchestrator.txt +1213 -0
- package/src/LLMClient/client.js +1375 -0
- package/src/LLMClient/converters/input-normalizer.js +238 -0
- package/src/LLMClient/converters/responses-to-claude.js +503 -0
- package/src/LLMClient/converters/responses-to-gemini.js +648 -0
- package/src/LLMClient/converters/responses-to-ollama.js +348 -0
- package/src/LLMClient/converters/responses-to-zai.js +667 -0
- package/src/LLMClient/errors.js +398 -0
- package/src/LLMClient/index.js +36 -0
- package/src/ai_based/completion_judge.js +421 -0
- package/src/ai_based/orchestrator.js +527 -0
- package/src/ai_based/pip_package_installer.js +173 -0
- package/src/ai_based/pip_package_lookup.js +197 -0
- package/src/cli/mcp_cli.js +70 -0
- package/src/cli/mcp_commands.js +255 -0
- package/src/commands/agents.js +18 -0
- package/src/commands/apikey.js +55 -0
- package/src/commands/bg.js +140 -0
- package/src/commands/commands.js +56 -0
- package/src/commands/debug.js +54 -0
- package/src/commands/exit.js +19 -0
- package/src/commands/help.js +35 -0
- package/src/commands/mcp.js +128 -0
- package/src/commands/model.js +176 -0
- package/src/commands/setup.js +13 -0
- package/src/commands/skills.js +51 -0
- package/src/commands/tools.js +165 -0
- package/src/commands/viewer.js +147 -0
- package/src/config/ai_models.js +312 -0
- package/src/config/config.js +10 -0
- package/src/config/constants.js +71 -0
- package/src/config/feature_flags.js +15 -0
- package/src/frontend/App.js +1263 -0
- package/src/frontend/README.md +81 -0
- package/src/frontend/components/AutocompleteMenu.js +47 -0
- package/src/frontend/components/BackgroundProcessList.js +175 -0
- package/src/frontend/components/BlankLine.js +62 -0
- package/src/frontend/components/ConversationItem.js +893 -0
- package/src/frontend/components/CurrentModelView.js +43 -0
- package/src/frontend/components/FileDiffViewer.js +616 -0
- package/src/frontend/components/Footer.js +25 -0
- package/src/frontend/components/Header.js +42 -0
- package/src/frontend/components/HelpView.js +154 -0
- package/src/frontend/components/Input.js +344 -0
- package/src/frontend/components/LoadingIndicator.js +31 -0
- package/src/frontend/components/ModelListView.js +49 -0
- package/src/frontend/components/ModelUpdatedView.js +22 -0
- package/src/frontend/components/SessionSpinner.js +66 -0
- package/src/frontend/components/SetupWizard.js +242 -0
- package/src/frontend/components/StreamOutput.js +34 -0
- package/src/frontend/components/TodoList.js +56 -0
- package/src/frontend/components/ToolApprovalPrompt.js +452 -0
- package/src/frontend/design/themeColors.js +42 -0
- package/src/frontend/hooks/useCompletion.js +84 -0
- package/src/frontend/hooks/useFileCompletion.js +467 -0
- package/src/frontend/hooks/useKeypress.js +145 -0
- package/src/frontend/index.js +65 -0
- package/src/frontend/utils/GridRenderer.js +140 -0
- package/src/frontend/utils/InlineFormatter.js +156 -0
- package/src/frontend/utils/diffUtils.js +235 -0
- package/src/frontend/utils/inputBuffer.js +441 -0
- package/src/frontend/utils/markdownParser.js +377 -0
- package/src/frontend/utils/outputRedirector.js +47 -0
- package/src/frontend/utils/renderInkComponent.js +42 -0
- package/src/frontend/utils/syntaxHighlighter.js +149 -0
- package/src/frontend/utils/toolUIFormatter.js +261 -0
- package/src/system/agents_loader.js +170 -0
- package/src/system/ai_request.js +737 -0
- package/src/system/background_process.js +317 -0
- package/src/system/code_executer.js +1233 -0
- package/src/system/command_loader.js +40 -0
- package/src/system/command_parser.js +133 -0
- package/src/system/conversation_state.js +265 -0
- package/src/system/conversation_trimmer.js +265 -0
- package/src/system/custom_command_loader.js +395 -0
- package/src/system/file_integrity.js +466 -0
- package/src/system/import_analyzer.py +174 -0
- package/src/system/log.js +82 -0
- package/src/system/mcp_integration.js +304 -0
- package/src/system/output_helper.js +89 -0
- package/src/system/session.js +1393 -0
- package/src/system/session_memory.js +481 -0
- package/src/system/skill_loader.js +324 -0
- package/src/system/system_info.js +483 -0
- package/src/system/tool_approval.js +160 -0
- package/src/system/tool_registry.js +184 -0
- package/src/system/ui_events.js +279 -0
- package/src/tools/code_editor.js +792 -0
- package/src/tools/file_reader.js +385 -0
- package/src/tools/glob.js +263 -0
- package/src/tools/response_message.js +30 -0
- package/src/tools/ripgrep.js +554 -0
- package/src/tools/skill_tool.js +122 -0
- package/src/tools/todo_write.js +182 -0
- package/src/tools/web_download.py +74 -0
- package/src/tools/web_downloader.js +83 -0
- package/src/util/clone.js +174 -0
- package/src/util/config.js +203 -0
- package/src/util/config_migration.js +174 -0
- package/src/util/debug_log.js +49 -0
- package/src/util/exit_handler.js +53 -0
- package/src/util/file_reference_parser.js +132 -0
- package/src/util/mcp_config_manager.js +159 -0
- package/src/util/output_formatter.js +50 -0
- package/src/util/path_helper.js +27 -0
- package/src/util/path_validator.js +178 -0
- package/src/util/prompt_loader.js +184 -0
- package/src/util/rag_helper.js +101 -0
- package/src/util/safe_fs.js +645 -0
- package/src/util/setup_wizard.js +62 -0
- package/src/util/text_formatter.js +33 -0
- package/src/util/version_check.js +116 -0
|
@@ -0,0 +1,527 @@
|
|
|
1
|
+
import dotenv from "dotenv";
|
|
2
|
+
// 이 파일은 계획을 실제 행동으로 옮기기 위해 어떤 도구를 호출할지 결정합니다.
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { truncateWithOmit } from "../util/text_formatter.js";
|
|
5
|
+
import { createSystemMessage, createTodoReminder, createSystemReminder, createTrimmedFileReminder } from "../util/prompt_loader.js";
|
|
6
|
+
import { request, shouldRetryWithTrim, getModelForProvider } from "../system/ai_request.js";
|
|
7
|
+
import { supportsCaching } from "../config/ai_models.js";
|
|
8
|
+
import { cleanupOrphanOutputs, trimConversation, getTrimmedFileReads, clearTrimmedFileReads } from "../system/conversation_trimmer.js";
|
|
9
|
+
import { runPythonCodeSchema, bashSchema, getPersistentShell } from "../system/code_executer.js";
|
|
10
|
+
import { readFileSchema, readFileRangeSchema } from "../tools/file_reader.js";
|
|
11
|
+
import { writeFileSchema, editFileRangeSchema, editFileReplaceSchema } from "../tools/code_editor.js";
|
|
12
|
+
import { fetchWebPageSchema } from "../tools/web_downloader.js";
|
|
13
|
+
import { responseMessageSchema } from "../tools/response_message.js";
|
|
14
|
+
import { todoWriteSchema } from "../tools/todo_write.js";
|
|
15
|
+
import { ripgrepSchema } from "../tools/ripgrep.js";
|
|
16
|
+
import { globSearchSchema } from "../tools/glob.js";
|
|
17
|
+
import { skillSchema, generateSkillListForPrompt } from "../tools/skill_tool.js";
|
|
18
|
+
import { loadSettings } from "../util/config.js";
|
|
19
|
+
import { createDebugLogger } from "../util/debug_log.js";
|
|
20
|
+
import { getCurrentTodos } from "../system/session_memory.js";
|
|
21
|
+
import { loadAgentsMdForPrompt } from "../system/agents_loader.js";
|
|
22
|
+
dotenv.config({ quiet: true });
|
|
23
|
+
|
|
24
|
+
const debugLog = createDebugLogger('orchestrator.log', 'orchestrator');
|
|
25
|
+
|
|
26
|
+
const orchestratorConversation = [];
|
|
27
|
+
let orchestratorRequestOptions = null;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Orchestrator conversation을 초기화하거나 시스템 프롬프트를 업데이트합니다.
|
|
31
|
+
* 매 요청마다 프롬프트 파일을 새로 읽어서 변경사항을 즉시 반영합니다.
|
|
32
|
+
*
|
|
33
|
+
* 로그 분석에서 발견한 전략 적용:
|
|
34
|
+
* 1. 캐시 제어 (cache_control: ephemeral) - Z.AI/GLM 모델의 경우
|
|
35
|
+
* 2. System-Reminder 패턴 - TODO 상태 등을 별도 블록으로 분리
|
|
36
|
+
* 3. 다중 시스템 메시지 구조 - 캐시 효과 극대화
|
|
37
|
+
*/
|
|
38
|
+
async function ensureConversationInitialized() {
|
|
39
|
+
// 현재 모델 확인 (캐시 제어 여부 결정)
|
|
40
|
+
const currentModel = await getModelForProvider();
|
|
41
|
+
const useCache = supportsCaching(currentModel);
|
|
42
|
+
debugLog(`[ensureConversationInitialized] Model: ${currentModel}, Cache enabled: ${useCache}`);
|
|
43
|
+
|
|
44
|
+
// 매번 최신 시스템 프롬프트를 로드 (캐시 제어 옵션 적용)
|
|
45
|
+
// 셸의 실제 현재 작업 디렉토리를 가져옴 (셸 상태가 유지되므로 process.cwd()와 다를 수 있음)
|
|
46
|
+
const shellCwd = await getPersistentShell().getCwd() || process.cwd();
|
|
47
|
+
debugLog(`[ensureConversationInitialized] Shell CWD: ${shellCwd}, process.cwd(): ${process.cwd()}`);
|
|
48
|
+
const systemMessage = await createSystemMessage("orchestrator.txt", {
|
|
49
|
+
CWD: shellCwd,
|
|
50
|
+
OS: process.app_custom?.systemInfo?.os || 'unknown'
|
|
51
|
+
}, {
|
|
52
|
+
model: currentModel,
|
|
53
|
+
enableCache: useCache
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// 현재 todos 가져오기
|
|
57
|
+
const currentTodos = getCurrentTodos();
|
|
58
|
+
|
|
59
|
+
// 사용 가능한 스킬 목록 생성
|
|
60
|
+
const skillListText = await generateSkillListForPrompt();
|
|
61
|
+
debugLog(`[ensureConversationInitialized] Generated skill list: ${skillListText ? 'has skills' : 'no skills'}`);
|
|
62
|
+
|
|
63
|
+
// AGENTS.md 로드 (프로젝트별 에이전트 지침)
|
|
64
|
+
const agentsMdText = await loadAgentsMdForPrompt();
|
|
65
|
+
debugLog(`[ensureConversationInitialized] AGENTS.md: ${agentsMdText ? `loaded (${agentsMdText.length} chars)` : 'not found'}`);
|
|
66
|
+
|
|
67
|
+
// 시스템 메시지 구성 (캐싱 모델의 경우 다중 블록 구조)
|
|
68
|
+
let systemMessageEntry;
|
|
69
|
+
|
|
70
|
+
if (useCache && Array.isArray(systemMessage.content)) {
|
|
71
|
+
// 캐싱 모델: 다중 블록 구조 사용 (Claude Code 스타일)
|
|
72
|
+
const contentBlocks = [...systemMessage.content];
|
|
73
|
+
|
|
74
|
+
// AGENTS.md를 별도 블록으로 추가 (프로젝트별 지침)
|
|
75
|
+
if (agentsMdText) {
|
|
76
|
+
contentBlocks.push({
|
|
77
|
+
type: "input_text",
|
|
78
|
+
text: agentsMdText,
|
|
79
|
+
cache_control: { type: "ephemeral" } // 프로젝트가 바뀔 때만 변경되므로 캐시
|
|
80
|
+
});
|
|
81
|
+
debugLog(`[ensureConversationInitialized] Added AGENTS.md as separate block (cached)`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 스킬 목록을 별도 블록으로 추가
|
|
85
|
+
if (skillListText) {
|
|
86
|
+
contentBlocks.push({
|
|
87
|
+
type: "input_text",
|
|
88
|
+
text: skillListText
|
|
89
|
+
// 스킬 목록은 자주 변경될 수 있으므로 캐시하지 않음
|
|
90
|
+
});
|
|
91
|
+
debugLog(`[ensureConversationInitialized] Added skill list as separate block`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// TODO 리마인더를 별도 블록으로 추가 (캐시 대상 아님)
|
|
95
|
+
if (currentTodos && currentTodos.length > 0) {
|
|
96
|
+
const todoReminder = createTodoReminder(currentTodos);
|
|
97
|
+
contentBlocks.push({
|
|
98
|
+
type: "input_text",
|
|
99
|
+
text: todoReminder
|
|
100
|
+
// cache_control 없음 - 동적 데이터이므로
|
|
101
|
+
});
|
|
102
|
+
debugLog(`[ensureConversationInitialized] Added ${currentTodos.length} todos as separate block (uncached)`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Trim된 파일 읽기 알림 추가 (파일 경로 알림 기능)
|
|
106
|
+
const trimmedFiles = getTrimmedFileReads();
|
|
107
|
+
if (trimmedFiles.length > 0) {
|
|
108
|
+
const trimmedFileReminder = createTrimmedFileReminder(trimmedFiles);
|
|
109
|
+
if (trimmedFileReminder) {
|
|
110
|
+
contentBlocks.push({
|
|
111
|
+
type: "input_text",
|
|
112
|
+
text: trimmedFileReminder
|
|
113
|
+
// cache_control 없음 - 동적 데이터이므로
|
|
114
|
+
});
|
|
115
|
+
debugLog(`[ensureConversationInitialized] Added trimmed file reminder for ${trimmedFiles.length} files (uncached)`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
systemMessageEntry = {
|
|
120
|
+
role: "system",
|
|
121
|
+
content: contentBlocks
|
|
122
|
+
};
|
|
123
|
+
} else {
|
|
124
|
+
// 일반 모델: 기존 방식 유지
|
|
125
|
+
let systemMessageText = systemMessage.content;
|
|
126
|
+
|
|
127
|
+
// AGENTS.md 추가 (프로젝트별 지침)
|
|
128
|
+
if (agentsMdText) {
|
|
129
|
+
systemMessageText += agentsMdText;
|
|
130
|
+
debugLog(`[ensureConversationInitialized] Added AGENTS.md to system message`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// 스킬 목록 추가
|
|
134
|
+
if (skillListText) {
|
|
135
|
+
systemMessageText += skillListText;
|
|
136
|
+
debugLog(`[ensureConversationInitialized] Added skill list to system message`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (currentTodos && currentTodos.length > 0) {
|
|
140
|
+
const todosSection = '\n\n## Current Task List (Your TODO tracker)\n\n' +
|
|
141
|
+
'You have created the following task list to track your work. Continue working on these tasks systematically:\n\n' +
|
|
142
|
+
currentTodos.map((todo, index) => {
|
|
143
|
+
const statusIcon = todo.status === 'completed' ? '✓' :
|
|
144
|
+
todo.status === 'in_progress' ? '→' : '○';
|
|
145
|
+
return `${index + 1}. [${statusIcon}] ${todo.content} (${todo.status})`;
|
|
146
|
+
}).join('\n') +
|
|
147
|
+
'\n\n**Remember**: Mark tasks as completed immediately after finishing them. Keep exactly ONE task as in_progress at a time.\n';
|
|
148
|
+
|
|
149
|
+
systemMessageText += todosSection;
|
|
150
|
+
debugLog(`[ensureConversationInitialized] Added ${currentTodos.length} todos to system message`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Trim된 파일 읽기 알림 추가 (파일 경로 알림 기능)
|
|
154
|
+
const trimmedFiles = getTrimmedFileReads();
|
|
155
|
+
if (trimmedFiles.length > 0) {
|
|
156
|
+
const trimmedFileReminder = createTrimmedFileReminder(trimmedFiles);
|
|
157
|
+
if (trimmedFileReminder) {
|
|
158
|
+
systemMessageText += '\n\n' + trimmedFileReminder;
|
|
159
|
+
debugLog(`[ensureConversationInitialized] Added trimmed file reminder for ${trimmedFiles.length} files`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
systemMessageEntry = {
|
|
164
|
+
role: "system",
|
|
165
|
+
content: [
|
|
166
|
+
{
|
|
167
|
+
type: "input_text",
|
|
168
|
+
text: systemMessageText
|
|
169
|
+
}
|
|
170
|
+
]
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// conversation이 비어있으면 새로 추가
|
|
175
|
+
if (!orchestratorConversation.length) {
|
|
176
|
+
orchestratorConversation.push(systemMessageEntry);
|
|
177
|
+
} else {
|
|
178
|
+
// conversation이 있으면 첫 번째 system 메시지를 업데이트
|
|
179
|
+
if (orchestratorConversation[0]?.role === "system") {
|
|
180
|
+
orchestratorConversation[0] = systemMessageEntry;
|
|
181
|
+
} else {
|
|
182
|
+
// system 메시지가 맨 앞에 없으면 추가
|
|
183
|
+
orchestratorConversation.unshift(systemMessageEntry);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function appendResponseToConversation(response) {
|
|
189
|
+
if (!response?.output || !Array.isArray(response.output)) {
|
|
190
|
+
debugLog(`[appendResponseToConversation] No output to append`);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
debugLog(`[appendResponseToConversation] Appending ${response.output.length} items from response`);
|
|
195
|
+
|
|
196
|
+
for (const item of response.output) {
|
|
197
|
+
if (!item) continue;
|
|
198
|
+
debugLog(`[appendResponseToConversation] Appending item type: ${item.type}, role: ${item.role}, tool_calls: ${item.tool_calls?.length || 0}`);
|
|
199
|
+
orchestratorConversation.push(JSON.parse(JSON.stringify(item)));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
debugLog(`[appendResponseToConversation] Conversation now has ${orchestratorConversation.length} entries`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function resetOrchestratorConversation() {
|
|
206
|
+
orchestratorConversation.length = 0;
|
|
207
|
+
orchestratorRequestOptions = null;
|
|
208
|
+
clearTrimmedFileReads(); // 세션 초기화 시 trim된 파일 목록도 초기화
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function getOrchestratorConversation() {
|
|
212
|
+
return orchestratorConversation;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function restoreOrchestratorConversation(savedConversation, savedRequestOptions = null) {
|
|
216
|
+
orchestratorConversation.length = 0;
|
|
217
|
+
if (Array.isArray(savedConversation)) {
|
|
218
|
+
orchestratorConversation.push(...savedConversation);
|
|
219
|
+
}
|
|
220
|
+
if (savedRequestOptions) {
|
|
221
|
+
orchestratorRequestOptions = savedRequestOptions;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function recordOrchestratorToolResult({ toolCallId, toolName = '', arguments: toolArguments = null, stdout = '', stderr = '', exitCode = null, originalResult = null }) {
|
|
226
|
+
debugLog(`[recordOrchestratorToolResult] Called - toolName: ${toolName}, toolCallId: ${toolCallId}, stdout: ${stdout?.length || 0} bytes, stderr: ${stderr?.length || 0} bytes`);
|
|
227
|
+
|
|
228
|
+
if (!toolCallId) {
|
|
229
|
+
debugLog(`[recordOrchestratorToolResult] No toolCallId provided, returning null`);
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const payload = {
|
|
234
|
+
tool: toolName || null,
|
|
235
|
+
call_id: toolCallId,
|
|
236
|
+
exit_code: exitCode,
|
|
237
|
+
stdout: stdout ? truncateWithOmit(stdout, 8000) : '',
|
|
238
|
+
stderr: stderr ? truncateWithOmit(stderr, 4000) : '',
|
|
239
|
+
original_result: originalResult // 원본 도구 결과 저장
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
debugLog(`[recordOrchestratorToolResult] payload - stdout: ${payload.stdout.length} bytes, stderr: ${payload.stderr.length} bytes, stderr content: "${payload.stderr}"`);
|
|
243
|
+
|
|
244
|
+
const toolResult = {
|
|
245
|
+
type: "function_call_output",
|
|
246
|
+
call_id: toolCallId,
|
|
247
|
+
output: JSON.stringify(payload)
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
debugLog(`[recordOrchestratorToolResult] toolResult output length: ${toolResult.output.length} bytes`);
|
|
251
|
+
orchestratorConversation.push(toolResult);
|
|
252
|
+
return toolResult;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// 대화 cleanup 및 trim 함수는 conversation_trimmer 모듈에서 가져옴
|
|
256
|
+
|
|
257
|
+
async function dispatchOrchestratorRequest({ toolChoice }) {
|
|
258
|
+
debugLog(`[dispatchOrchestratorRequest] START - toolChoice: ${toolChoice}`);
|
|
259
|
+
|
|
260
|
+
if (!orchestratorRequestOptions) {
|
|
261
|
+
debugLog(`[dispatchOrchestratorRequest] ERROR - orchestratorRequestOptions not initialized`);
|
|
262
|
+
throw new Error('Orchestrator request options not initialized.');
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
debugLog(`[dispatchOrchestratorRequest] Options - model: ${orchestratorRequestOptions.model}, tools count: ${orchestratorRequestOptions.tools?.length || 0}`);
|
|
266
|
+
|
|
267
|
+
let attemptCount = 0;
|
|
268
|
+
while (true) {
|
|
269
|
+
attemptCount++;
|
|
270
|
+
debugLog(`[dispatchOrchestratorRequest] Attempt ${attemptCount} - starting cleanup...`);
|
|
271
|
+
cleanupOrphanOutputs(orchestratorConversation);
|
|
272
|
+
|
|
273
|
+
debugLog(`[dispatchOrchestratorRequest] Conversation has ${orchestratorConversation.length} entries`);
|
|
274
|
+
|
|
275
|
+
// Conversation 구조 분석
|
|
276
|
+
const conversationTypes = {};
|
|
277
|
+
orchestratorConversation.forEach(entry => {
|
|
278
|
+
const type = entry?.type || entry?.role || 'unknown';
|
|
279
|
+
conversationTypes[type] = (conversationTypes[type] || 0) + 1;
|
|
280
|
+
});
|
|
281
|
+
debugLog(`[dispatchOrchestratorRequest] Conversation structure: ${JSON.stringify(conversationTypes)}`);
|
|
282
|
+
|
|
283
|
+
// function_call_output 항목들 확인
|
|
284
|
+
const functionCallOutputs = orchestratorConversation.filter(item => item.type === 'function_call_output');
|
|
285
|
+
debugLog(`[dispatchOrchestratorRequest] Found ${functionCallOutputs.length} function_call_output entries`);
|
|
286
|
+
|
|
287
|
+
functionCallOutputs.forEach((item, index) => {
|
|
288
|
+
debugLog(`[dispatchOrchestratorRequest] function_call_output[${index}] - call_id: ${item.call_id}, output length: ${item.output?.length || 0}, output preview: ${item.output?.substring(0, 200)}`);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
const { model, isGpt5Model, tools, taskName } = orchestratorRequestOptions;
|
|
292
|
+
const requestConfig = {
|
|
293
|
+
model,
|
|
294
|
+
input: orchestratorConversation,
|
|
295
|
+
reasoning: {},
|
|
296
|
+
tools,
|
|
297
|
+
tool_choice: toolChoice ?? "auto",
|
|
298
|
+
top_p: 1,
|
|
299
|
+
store: true
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
if (!isGpt5Model) {
|
|
303
|
+
requestConfig.temperature = 0;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
debugLog(`[dispatchOrchestratorRequest] Request config - model: ${model}, tool_choice: ${requestConfig.tool_choice}, tools: ${tools.length}, conversation entries: ${orchestratorConversation.length}`);
|
|
307
|
+
|
|
308
|
+
// Conversation 토큰 추정 (대략적)
|
|
309
|
+
const conversationJson = JSON.stringify(orchestratorConversation);
|
|
310
|
+
const estimatedTokens = Math.ceil(conversationJson.length / 4); // 대략 4 chars = 1 token
|
|
311
|
+
debugLog(`[dispatchOrchestratorRequest] Estimated tokens: ${estimatedTokens} (conversation size: ${conversationJson.length} bytes)`);
|
|
312
|
+
|
|
313
|
+
const apiStartTime = Date.now();
|
|
314
|
+
debugLog(`[dispatchOrchestratorRequest] >>>>> Sending API request at ${new Date(apiStartTime).toISOString()}...`);
|
|
315
|
+
|
|
316
|
+
try {
|
|
317
|
+
const response = await request(taskName, requestConfig);
|
|
318
|
+
const apiDuration = Date.now() - apiStartTime;
|
|
319
|
+
debugLog(`[dispatchOrchestratorRequest] <<<<< API request successful in ${apiDuration}ms`);
|
|
320
|
+
debugLog(`[dispatchOrchestratorRequest] Response outputs: ${response?.output?.length || 0}`);
|
|
321
|
+
if (response?.output) {
|
|
322
|
+
const outputTypes = {};
|
|
323
|
+
response.output.forEach(o => {
|
|
324
|
+
const type = o?.type || 'unknown';
|
|
325
|
+
outputTypes[type] = (outputTypes[type] || 0) + 1;
|
|
326
|
+
});
|
|
327
|
+
debugLog(`[dispatchOrchestratorRequest] Response output types: ${JSON.stringify(outputTypes)}`);
|
|
328
|
+
}
|
|
329
|
+
debugLog(`[dispatchOrchestratorRequest] END - success`);
|
|
330
|
+
// consolelog(JSON.stringify(response, null, 2));
|
|
331
|
+
return response;
|
|
332
|
+
} catch (error) {
|
|
333
|
+
const apiDuration = Date.now() - apiStartTime;
|
|
334
|
+
debugLog(`[dispatchOrchestratorRequest] <<<<< API request FAILED after ${apiDuration}ms: ${error.message}`);
|
|
335
|
+
debugLog(`[dispatchOrchestratorRequest] Error name: ${error.name}, type: ${error?.constructor?.name}`);
|
|
336
|
+
|
|
337
|
+
// AbortError는 즉시 전파 (세션 중단)
|
|
338
|
+
if (error.name === 'AbortError') {
|
|
339
|
+
debugLog(`[dispatchOrchestratorRequest] Request aborted by user, propagating AbortError`);
|
|
340
|
+
throw error;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (!shouldRetryWithTrim(error)) {
|
|
344
|
+
debugLog(`[dispatchOrchestratorRequest] Not recoverable by trimming, re-throwing`);
|
|
345
|
+
throw error;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
debugLog(`[dispatchOrchestratorRequest] Recoverable error detected, attempting to trim conversation...`);
|
|
349
|
+
const trimmed = trimConversation(orchestratorConversation);
|
|
350
|
+
debugLog(`[dispatchOrchestratorRequest] Trim result: ${trimmed}, new conversation length: ${orchestratorConversation.length}`);
|
|
351
|
+
if (!trimmed) {
|
|
352
|
+
debugLog(`[dispatchOrchestratorRequest] Cannot trim further, re-throwing error`);
|
|
353
|
+
throw error;
|
|
354
|
+
}
|
|
355
|
+
debugLog(`[dispatchOrchestratorRequest] Retrying with trimmed conversation...`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
export async function continueOrchestratorConversation() {
|
|
361
|
+
debugLog(`[continueOrchestratorConversation] START`);
|
|
362
|
+
debugLog(`[continueOrchestratorConversation] Current conversation length: ${orchestratorConversation.length}`);
|
|
363
|
+
|
|
364
|
+
// 매번 최신 프롬프트를 반영
|
|
365
|
+
await ensureConversationInitialized();
|
|
366
|
+
debugLog(`[continueOrchestratorConversation] Conversation initialized/updated`);
|
|
367
|
+
|
|
368
|
+
const response = await dispatchOrchestratorRequest({ toolChoice: "auto" });
|
|
369
|
+
debugLog(`[continueOrchestratorConversation] Received response, appending to conversation...`);
|
|
370
|
+
appendResponseToConversation(response);
|
|
371
|
+
debugLog(`[continueOrchestratorConversation] Conversation length after append: ${orchestratorConversation.length}`);
|
|
372
|
+
debugLog(`[continueOrchestratorConversation] END`);
|
|
373
|
+
return response;
|
|
374
|
+
}
|
|
375
|
+
// 쉘스크립트 코딩 규칙:
|
|
376
|
+
// - 사용자 확인이 필요한 명령은 피하고 -y 또는 -f 플래그로 자동 확인 설정
|
|
377
|
+
// - 출력이 과도한 명령은 피하고 필요한 경우 파일에 저장
|
|
378
|
+
// - 여러 명령은 && 연산자로 연결하여 중단 최소화
|
|
379
|
+
// - 명령 출력을 파이프 연산자로 전달하여 작업 단순화
|
|
380
|
+
// - 단순 계산은 비대화형 bc, 복잡한 수학 계산은 Python 사용; 절대 암산 금지
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
// 탐색 기반으로 현재 상황을 분석하고, 다음에 취할 행동을 AI에게 결정받습니다.
|
|
385
|
+
export async function orchestrateMission({ improvement_points = '', mcpToolSchemas = [], isAutoGenerated = false }) {
|
|
386
|
+
debugLog('');
|
|
387
|
+
debugLog('========================================');
|
|
388
|
+
debugLog('===== orchestrateMission START =========');
|
|
389
|
+
debugLog('========================================');
|
|
390
|
+
|
|
391
|
+
const taskName = 'orchestrator';
|
|
392
|
+
|
|
393
|
+
debugLog(`[orchestrateMission] Called with improvement_points: "${improvement_points?.substring(0, 100) || '(empty)'}", isAutoGenerated: ${isAutoGenerated}`);
|
|
394
|
+
debugLog(`[orchestrateMission] improvement_points length: ${improvement_points?.length || 0} characters`);
|
|
395
|
+
// mcpToolSchemas: MCP Integration에서 전달받은 MCP 도구들의 스키마 배열
|
|
396
|
+
debugLog(`[orchestrateMission] mcpToolSchemas count: ${mcpToolSchemas.length}`);
|
|
397
|
+
const improvementPointsText = typeof improvement_points === 'string' && improvement_points.trim().length ? improvement_points : '';
|
|
398
|
+
debugLog(`[orchestrateMission] improvementPointsText after processing: "${improvementPointsText.substring(0, 100) || '(empty)'}"`);
|
|
399
|
+
if (isAutoGenerated) {
|
|
400
|
+
debugLog(`[orchestrateMission] This is an auto-generated user message (from completion_judge)`);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
// Python 사용 가능 여부 확인
|
|
405
|
+
const hasPython = process.app_custom?.systemInfo?.commands?.hasPython || false;
|
|
406
|
+
debugLog(`[orchestrateMission] Python available: ${hasPython}`);
|
|
407
|
+
|
|
408
|
+
// 설정 로드
|
|
409
|
+
const settings = await loadSettings();
|
|
410
|
+
const toolsEnabled = settings?.TOOLS_ENABLED || {};
|
|
411
|
+
|
|
412
|
+
// 도구 이름과 스키마를 매핑
|
|
413
|
+
const availableTools = {
|
|
414
|
+
bash: bashSchema,
|
|
415
|
+
read_file: readFileSchema,
|
|
416
|
+
read_file_range: readFileRangeSchema,
|
|
417
|
+
write_file: writeFileSchema,
|
|
418
|
+
edit_file_range: editFileRangeSchema,
|
|
419
|
+
edit_file_replace: editFileReplaceSchema,
|
|
420
|
+
response_message: responseMessageSchema,
|
|
421
|
+
todo_write: todoWriteSchema,
|
|
422
|
+
ripgrep: ripgrepSchema,
|
|
423
|
+
glob_search: globSearchSchema,
|
|
424
|
+
invoke_skill: skillSchema // 스킬 호출 도구
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
// Python 관련 도구 (조건부 추가)
|
|
428
|
+
if (hasPython) {
|
|
429
|
+
availableTools.run_python_code = runPythonCodeSchema;
|
|
430
|
+
availableTools.fetch_web_page = fetchWebPageSchema;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// 활성화된 도구만 선택
|
|
434
|
+
const tools = [];
|
|
435
|
+
for (const [toolName, schema] of Object.entries(availableTools)) {
|
|
436
|
+
// 설정에 도구가 명시되어 있지 않거나 true로 설정된 경우 포함
|
|
437
|
+
if (toolsEnabled[toolName] !== false) {
|
|
438
|
+
tools.push(schema);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// MCP Tools 추가 (MCP 서버로부터 동적으로 제공받은 도구들)
|
|
443
|
+
// mcpToolSchemas는 MCP Integration이 연결된 MCP 서버들로부터 조회한 도구 스키마
|
|
444
|
+
// 각 스키마는 OpenAI function calling 형식으로 변환되어 있음
|
|
445
|
+
debugLog(`[orchestrateMission] Adding ${mcpToolSchemas.length} MCP tools to available tools`);
|
|
446
|
+
tools.push(...mcpToolSchemas);
|
|
447
|
+
debugLog(`[orchestrateMission] Total tools after MCP: ${tools.length}`);
|
|
448
|
+
|
|
449
|
+
// Planner가 선언한 도구 이름을 실제 함수 스키마로 연결해, LLM이 지시한 작업이 즉시 실행되도록 합니다.
|
|
450
|
+
tools.forEach(tool => {
|
|
451
|
+
tool.type = "function";
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
const model = await getModelForProvider();
|
|
455
|
+
const isGpt5Model = model.startsWith("gpt-5");
|
|
456
|
+
debugLog(`[orchestrateMission] Using model: ${model}, isGpt5Model: ${isGpt5Model}`);
|
|
457
|
+
|
|
458
|
+
orchestratorRequestOptions = {
|
|
459
|
+
model,
|
|
460
|
+
isGpt5Model,
|
|
461
|
+
tools,
|
|
462
|
+
taskName,
|
|
463
|
+
parallel_tool_calls: true
|
|
464
|
+
};
|
|
465
|
+
debugLog(`[orchestrateMission] orchestratorRequestOptions configured with ${tools.length} tools`);
|
|
466
|
+
|
|
467
|
+
await ensureConversationInitialized();
|
|
468
|
+
debugLog(`[orchestrateMission] Conversation initialized, current length: ${orchestratorConversation.length}`);
|
|
469
|
+
|
|
470
|
+
debugLog(`[orchestrateMission] Adding user message to conversation: "${improvementPointsText.substring(0, 100)}"`);
|
|
471
|
+
|
|
472
|
+
// 캐싱 모델인 경우 사용자 메시지에도 캐시 제어 적용 (Claude Code 스타일)
|
|
473
|
+
const useCache = supportsCaching(model);
|
|
474
|
+
const userContentBlocks = [];
|
|
475
|
+
|
|
476
|
+
// Auto-generated 메시지의 경우 system-reminder 추가
|
|
477
|
+
if (isAutoGenerated) {
|
|
478
|
+
const autoGenReminder = createSystemReminder(
|
|
479
|
+
'이 메시지는 시스템에서 자동 생성되었습니다. 사용자가 직접 입력한 것이 아닙니다.',
|
|
480
|
+
{ hideFromUser: true }
|
|
481
|
+
);
|
|
482
|
+
userContentBlocks.push({
|
|
483
|
+
type: "input_text",
|
|
484
|
+
text: autoGenReminder
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// 메인 사용자 메시지
|
|
489
|
+
const mainContent = {
|
|
490
|
+
type: "input_text",
|
|
491
|
+
text: improvementPointsText
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
// 캐싱 모델의 경우 사용자 메시지에도 캐시 적용 (반복되는 긴 메시지에 효과적)
|
|
495
|
+
if (useCache && improvementPointsText.length > 500) {
|
|
496
|
+
mainContent.cache_control = { type: "ephemeral" };
|
|
497
|
+
debugLog(`[orchestrateMission] Applied cache control to user message (${improvementPointsText.length} chars)`);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
userContentBlocks.push(mainContent);
|
|
501
|
+
|
|
502
|
+
const userMessage = {
|
|
503
|
+
role: "user",
|
|
504
|
+
content: userContentBlocks
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
// Auto-generated user message인 경우 _internal_only 플래그 추가
|
|
508
|
+
if (isAutoGenerated) {
|
|
509
|
+
userMessage._internal_only = true;
|
|
510
|
+
debugLog(`[_internal_only] Marked user message as internal-only (auto-generated from completion_judge)`);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
orchestratorConversation.push(userMessage);
|
|
514
|
+
debugLog(`[orchestrateMission] Conversation length after adding user message: ${orchestratorConversation.length}`);
|
|
515
|
+
|
|
516
|
+
debugLog(`[orchestrateMission] Dispatching orchestrator request with toolChoice: required`);
|
|
517
|
+
const response = await dispatchOrchestratorRequest({ toolChoice: "required" });
|
|
518
|
+
debugLog(`[orchestrateMission] Received response, appending to conversation...`);
|
|
519
|
+
appendResponseToConversation(response);
|
|
520
|
+
debugLog(`[orchestrateMission] Conversation length after response: ${orchestratorConversation.length}`);
|
|
521
|
+
|
|
522
|
+
debugLog('========================================');
|
|
523
|
+
debugLog('===== orchestrateMission END ===========');
|
|
524
|
+
debugLog('========================================');
|
|
525
|
+
debugLog('');
|
|
526
|
+
return response;
|
|
527
|
+
}
|