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
package/src/LLMClient/errors.js
CHANGED
|
@@ -176,15 +176,41 @@ export function normalizeError(error, provider) {
|
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
// Anthropic SDK error (check BEFORE OpenAI SDK error, as both have error.status && error.error)
|
|
179
|
-
|
|
179
|
+
// Z.AI uses Anthropic SDK so handle similarly
|
|
180
|
+
if ((provider === 'claude' || provider === 'zai') && error.status) {
|
|
180
181
|
// Parse Claude error from error.error object (Anthropic SDK provides parsed error)
|
|
181
182
|
let errorType = ERROR_TYPE_MAP[error.status] || 'api_error';
|
|
182
183
|
let code = null;
|
|
183
|
-
let message = error.message || 'Claude API error';
|
|
184
|
+
let message = error.message || (provider === 'zai' ? 'Z.AI API error' : 'Claude API error');
|
|
184
185
|
|
|
186
|
+
// Z.AI specific error format: { detail: [{ type, loc, msg, ... }] }
|
|
187
|
+
if (error.error && error.error.detail && Array.isArray(error.error.detail)) {
|
|
188
|
+
const zaiError = error.error.detail[0];
|
|
189
|
+
if (zaiError) {
|
|
190
|
+
code = zaiError.type || null;
|
|
191
|
+
message = zaiError.msg || message;
|
|
192
|
+
|
|
193
|
+
// Map Z.AI error types
|
|
194
|
+
if (zaiError.type === 'json_invalid') {
|
|
195
|
+
errorType = 'invalid_request_error';
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// Z.AI auth error format: { error: { message, type: "401" } }
|
|
200
|
+
else if (error.error && error.error.type === '401') {
|
|
201
|
+
errorType = 'authentication_error';
|
|
202
|
+
code = 'invalid_api_key';
|
|
203
|
+
message = error.error.message || 'Invalid or expired API key';
|
|
204
|
+
}
|
|
205
|
+
// Z.AI context exceeded format: { error: { code: "1210", message: "..." } }
|
|
206
|
+
else if (error.error && (error.error.code === '1210' || error.error.code === 1210)) {
|
|
207
|
+
errorType = 'invalid_request_error';
|
|
208
|
+
code = 'context_length_exceeded';
|
|
209
|
+
message = error.error.message || 'Context length exceeded';
|
|
210
|
+
}
|
|
185
211
|
// Anthropic SDK provides error.error object with structure:
|
|
186
212
|
// { type: 'error', error: { type: 'not_found_error', message: '...' } }
|
|
187
|
-
if (error.error && error.error.error) {
|
|
213
|
+
else if (error.error && error.error.error) {
|
|
188
214
|
const claudeError = error.error.error;
|
|
189
215
|
|
|
190
216
|
if (claudeError.type) {
|
|
@@ -220,7 +246,7 @@ export function normalizeError(error, provider) {
|
|
|
220
246
|
param: null,
|
|
221
247
|
code: code,
|
|
222
248
|
status: error.status,
|
|
223
|
-
provider:
|
|
249
|
+
provider: provider,
|
|
224
250
|
originalError: error
|
|
225
251
|
}
|
|
226
252
|
);
|
package/src/LLMClient/index.js
CHANGED
|
@@ -21,6 +21,11 @@ export {
|
|
|
21
21
|
convertOllamaResponseToResponsesFormat
|
|
22
22
|
} from './converters/responses-to-ollama.js';
|
|
23
23
|
|
|
24
|
+
export {
|
|
25
|
+
convertResponsesRequestToZaiFormat,
|
|
26
|
+
convertZaiResponseToResponsesFormat
|
|
27
|
+
} from './converters/responses-to-zai.js';
|
|
28
|
+
|
|
24
29
|
// Export error classes
|
|
25
30
|
export {
|
|
26
31
|
LLMError,
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import dotenv from "dotenv";
|
|
2
|
-
import { request,
|
|
2
|
+
import { request, getModelForProvider } from "../system/ai_request.js";
|
|
3
3
|
import { getOrchestratorConversation } from "./orchestrator.js";
|
|
4
4
|
import { createSystemMessage } from "../util/prompt_loader.js";
|
|
5
5
|
import { createDebugLogger } from "../util/debug_log.js";
|
|
6
6
|
import { getCurrentTodos } from "../system/session_memory.js";
|
|
7
|
-
import { cleanupOrphanOutputs, trimConversation } from "../system/conversation_trimmer.js";
|
|
8
7
|
|
|
9
8
|
dotenv.config({ quiet: true });
|
|
10
9
|
|
|
@@ -30,16 +29,6 @@ export const completionJudgmentSchema = {
|
|
|
30
29
|
strict: true
|
|
31
30
|
};
|
|
32
31
|
|
|
33
|
-
// systemMessage는 judgeMissionCompletion 호출 시 동적으로 생성됨
|
|
34
|
-
|
|
35
|
-
const completionJudgeConversation = [];
|
|
36
|
-
let lastOrchestratorSnapshotLength = 0;
|
|
37
|
-
|
|
38
|
-
function cloneMessage(message) {
|
|
39
|
-
return JSON.parse(JSON.stringify(message));
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
43
32
|
async function createCompletionJudgeRequestOptions() {
|
|
44
33
|
const model = await getModelForProvider();
|
|
45
34
|
return {
|
|
@@ -50,76 +39,150 @@ async function createCompletionJudgeRequestOptions() {
|
|
|
50
39
|
}
|
|
51
40
|
|
|
52
41
|
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
42
|
+
* Orchestrator 대화에서 요약된 컨텍스트를 추출합니다.
|
|
43
|
+
* 전체 대화 대신 핵심 정보만 텍스트로 요약하여 반환합니다.
|
|
44
|
+
*
|
|
45
|
+
* @returns {Object} { toolExecutionSummary: string, lastAssistantMessage: string }
|
|
55
46
|
*/
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
47
|
+
function extractSummarizedContext() {
|
|
48
|
+
debugLog(`[extractSummarizedContext] START`);
|
|
49
|
+
const sourceConversation = getOrchestratorConversation();
|
|
50
|
+
|
|
51
|
+
if (!Array.isArray(sourceConversation) || !sourceConversation.length) {
|
|
52
|
+
debugLog(`[extractSummarizedContext] No source conversation`);
|
|
53
|
+
return { toolExecutionSummary: '', lastAssistantMessage: '' };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
debugLog(`[extractSummarizedContext] Source conversation length: ${sourceConversation.length}`);
|
|
57
|
+
|
|
58
|
+
// 도구 실행 결과 추출
|
|
59
|
+
const toolExecutions = [];
|
|
60
|
+
const functionCalls = new Map(); // call_id -> { name, arguments }
|
|
61
|
+
|
|
62
|
+
for (const entry of sourceConversation) {
|
|
63
|
+
if (!entry) continue;
|
|
64
|
+
|
|
65
|
+
// function_call 정보 수집
|
|
66
|
+
if (entry.type === 'function_call') {
|
|
67
|
+
functionCalls.set(entry.call_id || entry.id, {
|
|
68
|
+
name: entry.name,
|
|
69
|
+
arguments: entry.arguments
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// function_call_output에서 결과 추출
|
|
74
|
+
if (entry.type === 'function_call_output') {
|
|
75
|
+
const callId = entry.call_id;
|
|
76
|
+
const callInfo = functionCalls.get(callId);
|
|
77
|
+
const toolName = callInfo?.name || 'unknown';
|
|
78
|
+
|
|
79
|
+
let success = true;
|
|
80
|
+
let exitCode = null;
|
|
81
|
+
let errorMessage = '';
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const output = JSON.parse(entry.output || '{}');
|
|
85
|
+
if (output.exit_code !== undefined) {
|
|
86
|
+
exitCode = output.exit_code;
|
|
87
|
+
success = exitCode === 0;
|
|
88
|
+
}
|
|
89
|
+
if (output.operation_successful === false) {
|
|
90
|
+
success = false;
|
|
91
|
+
}
|
|
92
|
+
if (output.error_message) {
|
|
93
|
+
errorMessage = output.error_message;
|
|
94
|
+
}
|
|
95
|
+
if (output.stderr && output.stderr.trim()) {
|
|
96
|
+
errorMessage = output.stderr.substring(0, 100);
|
|
97
|
+
}
|
|
98
|
+
} catch (e) {
|
|
99
|
+
// JSON 파싱 실패 시 무시
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 인자에서 주요 정보 추출 (파일 경로 등)
|
|
103
|
+
let targetInfo = '';
|
|
104
|
+
if (callInfo?.arguments) {
|
|
105
|
+
try {
|
|
106
|
+
const args = JSON.parse(callInfo.arguments);
|
|
107
|
+
if (args.file_path) targetInfo = ` (${args.file_path})`;
|
|
108
|
+
else if (args.path) targetInfo = ` (${args.path})`;
|
|
109
|
+
else if (args.script) targetInfo = ` (bash)`;
|
|
110
|
+
else if (args.code) targetInfo = ` (python)`;
|
|
111
|
+
} catch (e) {}
|
|
66
112
|
}
|
|
67
|
-
]
|
|
68
|
-
};
|
|
69
113
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (completionJudgeConversation[0]?.role === "system") {
|
|
76
|
-
completionJudgeConversation[0] = systemMessageEntry;
|
|
77
|
-
} else {
|
|
78
|
-
// system 메시지가 맨 앞에 없으면 추가
|
|
79
|
-
completionJudgeConversation.unshift(systemMessageEntry);
|
|
114
|
+
const status = success ? '✓' : '✗';
|
|
115
|
+
const exitCodeStr = exitCode !== null ? ` [exit=${exitCode}]` : '';
|
|
116
|
+
const errorStr = !success && errorMessage ? ` - ${errorMessage}` : '';
|
|
117
|
+
|
|
118
|
+
toolExecutions.push(`${status} ${toolName}${targetInfo}${exitCodeStr}${errorStr}`);
|
|
80
119
|
}
|
|
81
120
|
}
|
|
121
|
+
|
|
122
|
+
// 마지막 assistant 메시지 추출
|
|
123
|
+
let lastAssistantMessage = '';
|
|
124
|
+
for (let i = sourceConversation.length - 1; i >= 0; i--) {
|
|
125
|
+
const entry = sourceConversation[i];
|
|
126
|
+
if (entry?.type === 'message' && entry?.role === 'assistant') {
|
|
127
|
+
const textContent = entry.content?.find(c => c.type === 'output_text');
|
|
128
|
+
if (textContent?.text) {
|
|
129
|
+
lastAssistantMessage = textContent.text;
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const toolExecutionSummary = toolExecutions.length > 0
|
|
136
|
+
? toolExecutions.join('\n')
|
|
137
|
+
: '(도구 실행 없음)';
|
|
138
|
+
|
|
139
|
+
debugLog(`[extractSummarizedContext] Tool executions: ${toolExecutions.length}`);
|
|
140
|
+
debugLog(`[extractSummarizedContext] Last assistant message length: ${lastAssistantMessage.length}`);
|
|
141
|
+
debugLog(`[extractSummarizedContext] END`);
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
toolExecutionSummary,
|
|
145
|
+
lastAssistantMessage
|
|
146
|
+
};
|
|
82
147
|
}
|
|
83
148
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
debugLog(`[syncOrchestratorConversation] Current completionJudge conversation length: ${completionJudgeConversation.length}`);
|
|
149
|
+
/**
|
|
150
|
+
* 요약된 컨텍스트를 사용자 메시지로 포맷팅합니다.
|
|
151
|
+
*/
|
|
152
|
+
function buildContextMessage(mission, summarizedContext, currentTodos) {
|
|
153
|
+
const parts = [];
|
|
90
154
|
|
|
91
|
-
|
|
92
|
-
debugLog(`[syncOrchestratorConversation] No source conversation to sync`);
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
155
|
+
parts.push(`## 원래 사용자 요청\n${mission}`);
|
|
95
156
|
|
|
96
|
-
|
|
97
|
-
for (let i = lastOrchestratorSnapshotLength; i < sourceConversation.length; i++) {
|
|
98
|
-
const entry = sourceConversation[i];
|
|
99
|
-
if (!entry) continue;
|
|
157
|
+
parts.push(`\n## 실행된 도구들\n${summarizedContext.toolExecutionSummary}`);
|
|
100
158
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
159
|
+
if (summarizedContext.lastAssistantMessage) {
|
|
160
|
+
// 너무 길면 truncate
|
|
161
|
+
const maxLength = 2000;
|
|
162
|
+
let assistantMsg = summarizedContext.lastAssistantMessage;
|
|
163
|
+
if (assistantMsg.length > maxLength) {
|
|
164
|
+
assistantMsg = assistantMsg.substring(0, maxLength) + '\n... (truncated)';
|
|
105
165
|
}
|
|
166
|
+
parts.push(`\n## 에이전트의 마지막 응답\n${assistantMsg}`);
|
|
167
|
+
}
|
|
106
168
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
169
|
+
if (currentTodos && currentTodos.length > 0) {
|
|
170
|
+
const todoList = currentTodos.map((todo, index) => {
|
|
171
|
+
const statusIcon = todo.status === 'completed' ? '✓' :
|
|
172
|
+
todo.status === 'in_progress' ? '→' : '○';
|
|
173
|
+
return `${index + 1}. [${statusIcon}] ${todo.content} (${todo.status})`;
|
|
174
|
+
}).join('\n');
|
|
175
|
+
parts.push(`\n## 현재 TODO 상태\n${todoList}`);
|
|
112
176
|
}
|
|
113
177
|
|
|
114
|
-
|
|
115
|
-
debugLog(`[syncOrchestratorConversation] Synced ${syncedCount} entries, new snapshot length: ${lastOrchestratorSnapshotLength}`);
|
|
116
|
-
debugLog(`[syncOrchestratorConversation] CompletionJudge conversation now has ${completionJudgeConversation.length} entries`);
|
|
117
|
-
debugLog(`[syncOrchestratorConversation] END`);
|
|
178
|
+
return parts.join('\n');
|
|
118
179
|
}
|
|
119
180
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
181
|
+
/**
|
|
182
|
+
* 독립적인 컨텍스트로 completion judge 요청을 보냅니다.
|
|
183
|
+
* Orchestrator 대화를 복사하지 않고, 요약된 정보만 전달합니다.
|
|
184
|
+
*/
|
|
185
|
+
async function dispatchCompletionJudgeRequest(options, summarizedContext) {
|
|
123
186
|
debugLog(`[dispatchCompletionJudgeRequest] START`);
|
|
124
187
|
|
|
125
188
|
if (!options) {
|
|
@@ -127,110 +190,89 @@ async function dispatchCompletionJudgeRequest(options) {
|
|
|
127
190
|
throw new Error('Completion judge request options not initialized.');
|
|
128
191
|
}
|
|
129
192
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
let attemptCount = 0;
|
|
133
|
-
while (true) {
|
|
134
|
-
attemptCount++;
|
|
135
|
-
debugLog(`[dispatchCompletionJudgeRequest] Attempt ${attemptCount} - starting cleanup...`);
|
|
136
|
-
cleanupOrphanOutputs(completionJudgeConversation);
|
|
137
|
-
|
|
138
|
-
debugLog(`[dispatchCompletionJudgeRequest] Conversation has ${completionJudgeConversation.length} entries`);
|
|
139
|
-
|
|
140
|
-
// Conversation 구조 분석
|
|
141
|
-
const conversationTypes = {};
|
|
142
|
-
completionJudgeConversation.forEach(entry => {
|
|
143
|
-
const type = entry?.type || entry?.role || 'unknown';
|
|
144
|
-
conversationTypes[type] = (conversationTypes[type] || 0) + 1;
|
|
145
|
-
});
|
|
146
|
-
debugLog(`[dispatchCompletionJudgeRequest] Conversation structure: ${JSON.stringify(conversationTypes)}`);
|
|
147
|
-
|
|
148
|
-
// function_call_output 항목들 확인
|
|
149
|
-
const functionCallOutputs = completionJudgeConversation.filter(item => item.type === 'function_call_output');
|
|
150
|
-
debugLog(`[dispatchCompletionJudgeRequest] Found ${functionCallOutputs.length} function_call_output entries`);
|
|
151
|
-
|
|
152
|
-
functionCallOutputs.forEach((item, index) => {
|
|
153
|
-
debugLog(`[dispatchCompletionJudgeRequest] function_call_output[${index}] - call_id: ${item.call_id}, output length: ${item.output?.length || 0}, output preview: ${item.output?.substring(0, 200)}`);
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
const { model, isGpt5Model, taskName } = options;
|
|
157
|
-
|
|
158
|
-
const requestPayload = {
|
|
159
|
-
model,
|
|
160
|
-
input: completionJudgeConversation,
|
|
161
|
-
text: {
|
|
162
|
-
format: {
|
|
163
|
-
type: "json_schema",
|
|
164
|
-
name: completionJudgmentSchema.name,
|
|
165
|
-
strict: completionJudgmentSchema.strict,
|
|
166
|
-
schema: completionJudgmentSchema.schema
|
|
167
|
-
}
|
|
168
|
-
},
|
|
169
|
-
reasoning: {},
|
|
170
|
-
tool_choice: "none",
|
|
171
|
-
tools: [],
|
|
172
|
-
top_p: 1,
|
|
173
|
-
store: true
|
|
174
|
-
};
|
|
193
|
+
const { model, isGpt5Model, taskName, systemMessage, mission, currentTodos } = options;
|
|
194
|
+
debugLog(`[dispatchCompletionJudgeRequest] Options - model: ${model}, taskName: ${taskName}`);
|
|
175
195
|
|
|
176
|
-
|
|
177
|
-
|
|
196
|
+
// 독립적인 대화 구성 (도구 호출 기록 없음)
|
|
197
|
+
const conversation = [
|
|
198
|
+
// System 메시지
|
|
199
|
+
{
|
|
200
|
+
role: "system",
|
|
201
|
+
content: [{ type: "input_text", text: systemMessage }]
|
|
202
|
+
},
|
|
203
|
+
// User 메시지: 요약된 컨텍스트
|
|
204
|
+
{
|
|
205
|
+
role: "user",
|
|
206
|
+
content: [{
|
|
207
|
+
type: "input_text",
|
|
208
|
+
text: buildContextMessage(mission, summarizedContext, currentTodos)
|
|
209
|
+
}]
|
|
178
210
|
}
|
|
211
|
+
];
|
|
179
212
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
debugLog(`[dispatchCompletionJudgeRequest] Response output_text length: ${response?.output_text?.length || 0}`);
|
|
190
|
-
debugLog(`[dispatchCompletionJudgeRequest] END - success`);
|
|
191
|
-
return response;
|
|
192
|
-
} catch (error) {
|
|
193
|
-
debugLog(`[dispatchCompletionJudgeRequest] API request failed: ${error.message}`);
|
|
194
|
-
debugLog(`[dispatchCompletionJudgeRequest] Error name: ${error.name}, type: ${error?.constructor?.name}`);
|
|
195
|
-
|
|
196
|
-
// AbortError는 즉시 전파 (세션 중단)
|
|
197
|
-
if (error.name === 'AbortError') {
|
|
198
|
-
debugLog(`[dispatchCompletionJudgeRequest] Request aborted by user, propagating AbortError`);
|
|
199
|
-
throw error;
|
|
213
|
+
const requestPayload = {
|
|
214
|
+
model,
|
|
215
|
+
input: conversation,
|
|
216
|
+
text: {
|
|
217
|
+
format: {
|
|
218
|
+
type: "json_schema",
|
|
219
|
+
name: completionJudgmentSchema.name,
|
|
220
|
+
strict: completionJudgmentSchema.strict,
|
|
221
|
+
schema: completionJudgmentSchema.schema
|
|
200
222
|
}
|
|
223
|
+
},
|
|
224
|
+
reasoning: {},
|
|
225
|
+
tool_choice: "none",
|
|
226
|
+
tools: [],
|
|
227
|
+
top_p: 1,
|
|
228
|
+
store: true
|
|
229
|
+
};
|
|
201
230
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}
|
|
231
|
+
if (!isGpt5Model) {
|
|
232
|
+
requestPayload.temperature = 0;
|
|
233
|
+
}
|
|
206
234
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
235
|
+
// 토큰 추정
|
|
236
|
+
const conversationJson = JSON.stringify(conversation);
|
|
237
|
+
const estimatedTokens = Math.ceil(conversationJson.length / 4);
|
|
238
|
+
debugLog(`[dispatchCompletionJudgeRequest] Estimated tokens: ${estimatedTokens} (conversation size: ${conversationJson.length} bytes)`);
|
|
239
|
+
|
|
240
|
+
const apiStartTime = Date.now();
|
|
241
|
+
debugLog(`[dispatchCompletionJudgeRequest] >>>>> Sending API request at ${new Date(apiStartTime).toISOString()}...`);
|
|
242
|
+
|
|
243
|
+
try {
|
|
244
|
+
const response = await request(taskName, requestPayload);
|
|
245
|
+
const apiDuration = Date.now() - apiStartTime;
|
|
246
|
+
debugLog(`[dispatchCompletionJudgeRequest] <<<<< API request successful in ${apiDuration}ms`);
|
|
247
|
+
debugLog(`[dispatchCompletionJudgeRequest] Response output_text length: ${response?.output_text?.length || 0}`);
|
|
248
|
+
debugLog(`[dispatchCompletionJudgeRequest] END - success`);
|
|
249
|
+
return response;
|
|
250
|
+
} catch (error) {
|
|
251
|
+
const apiDuration = Date.now() - apiStartTime;
|
|
252
|
+
debugLog(`[dispatchCompletionJudgeRequest] <<<<< API request FAILED after ${apiDuration}ms: ${error.message}`);
|
|
253
|
+
debugLog(`[dispatchCompletionJudgeRequest] Error name: ${error.name}, type: ${error?.constructor?.name}`);
|
|
254
|
+
|
|
255
|
+
// AbortError는 즉시 전파 (세션 중단)
|
|
256
|
+
if (error.name === 'AbortError') {
|
|
257
|
+
debugLog(`[dispatchCompletionJudgeRequest] Request aborted by user, propagating AbortError`);
|
|
258
|
+
throw error;
|
|
215
259
|
}
|
|
260
|
+
|
|
261
|
+
throw error;
|
|
216
262
|
}
|
|
217
263
|
}
|
|
218
264
|
|
|
265
|
+
// Legacy exports (하위 호환성 - 실제로는 더 이상 사용하지 않음)
|
|
219
266
|
export function resetCompletionJudgeConversation() {
|
|
220
|
-
|
|
221
|
-
lastOrchestratorSnapshotLength = 0;
|
|
267
|
+
// No-op: 독립 컨텍스트 방식에서는 대화를 유지하지 않음
|
|
222
268
|
}
|
|
223
269
|
|
|
224
270
|
export function getCompletionJudgeConversation() {
|
|
225
|
-
return
|
|
271
|
+
return [];
|
|
226
272
|
}
|
|
227
273
|
|
|
228
274
|
export function restoreCompletionJudgeConversation(savedConversation, savedSnapshotLength = 0) {
|
|
229
|
-
|
|
230
|
-
if (Array.isArray(savedConversation)) {
|
|
231
|
-
completionJudgeConversation.push(...savedConversation);
|
|
232
|
-
}
|
|
233
|
-
lastOrchestratorSnapshotLength = savedSnapshotLength;
|
|
275
|
+
// No-op: 독립 컨텍스트 방식에서는 복원할 대화가 없음
|
|
234
276
|
}
|
|
235
277
|
|
|
236
278
|
/**
|
|
@@ -246,49 +288,77 @@ export async function judgeMissionCompletion(templateVars = {}) {
|
|
|
246
288
|
debugLog('=== judgeMissionCompletion START =======');
|
|
247
289
|
debugLog('========================================');
|
|
248
290
|
debugLog(`[judgeMissionCompletion] Called with templateVars: ${JSON.stringify(Object.keys(templateVars))}`);
|
|
249
|
-
|
|
250
|
-
|
|
291
|
+
|
|
292
|
+
const mission = templateVars.what_user_requests || '';
|
|
293
|
+
if (mission) {
|
|
294
|
+
debugLog(`[judgeMissionCompletion] what_user_requests: ${mission.substring(0, 200)}`);
|
|
251
295
|
}
|
|
252
296
|
|
|
253
297
|
try {
|
|
254
298
|
debugLog(`[judgeMissionCompletion] Creating request options...`);
|
|
255
|
-
const
|
|
256
|
-
debugLog(`[judgeMissionCompletion] Request options - model: ${
|
|
299
|
+
const baseOptions = await createCompletionJudgeRequestOptions();
|
|
300
|
+
debugLog(`[judgeMissionCompletion] Request options - model: ${baseOptions.model}, isGpt5Model: ${baseOptions.isGpt5Model}`);
|
|
257
301
|
|
|
258
|
-
// 현재 todos를
|
|
302
|
+
// 현재 todos를 가져옴
|
|
259
303
|
const currentTodos = getCurrentTodos();
|
|
260
304
|
debugLog(`[judgeMissionCompletion] Current todos count: ${currentTodos.length}`);
|
|
261
305
|
|
|
262
|
-
//
|
|
306
|
+
// 시스템 메시지 로드
|
|
263
307
|
const enhancedTemplateVars = {
|
|
264
308
|
...templateVars,
|
|
265
309
|
currentTodos: currentTodos
|
|
266
310
|
};
|
|
311
|
+
const systemMessageObj = await createSystemMessage("completion_judge.txt", enhancedTemplateVars);
|
|
312
|
+
debugLog(`[judgeMissionCompletion] System message loaded`);
|
|
313
|
+
|
|
314
|
+
// Orchestrator 대화에서 요약된 컨텍스트 추출
|
|
315
|
+
debugLog(`[judgeMissionCompletion] Extracting summarized context...`);
|
|
316
|
+
const summarizedContext = extractSummarizedContext();
|
|
317
|
+
debugLog(`[judgeMissionCompletion] Context extracted - tools: ${summarizedContext.toolExecutionSummary.split('\n').length}, assistant msg len: ${summarizedContext.lastAssistantMessage.length}`);
|
|
318
|
+
|
|
319
|
+
// 옵션에 추가 정보 포함
|
|
320
|
+
const requestOptions = {
|
|
321
|
+
...baseOptions,
|
|
322
|
+
systemMessage: systemMessageObj.content,
|
|
323
|
+
mission: mission,
|
|
324
|
+
currentTodos: currentTodos
|
|
325
|
+
};
|
|
267
326
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
await ensureCompletionJudgeConversationInitialized(enhancedTemplateVars);
|
|
271
|
-
debugLog(`[judgeMissionCompletion] Conversation initialized, length: ${completionJudgeConversation.length}`);
|
|
272
|
-
|
|
273
|
-
// Orchestrator의 대화 내용 동기화 (system 메시지 제외)
|
|
274
|
-
debugLog(`[judgeMissionCompletion] Syncing orchestrator conversation...`);
|
|
275
|
-
syncOrchestratorConversation();
|
|
276
|
-
debugLog(`[judgeMissionCompletion] Sync complete, conversation length: ${completionJudgeConversation.length}`);
|
|
277
|
-
|
|
278
|
-
debugLog(`[judgeMissionCompletion] Sending request with ${completionJudgeConversation.length} conversation entries`);
|
|
279
|
-
|
|
280
|
-
const response = await dispatchCompletionJudgeRequest(requestOptions);
|
|
327
|
+
debugLog(`[judgeMissionCompletion] Sending request with summarized context...`);
|
|
328
|
+
const response = await dispatchCompletionJudgeRequest(requestOptions, summarizedContext);
|
|
281
329
|
|
|
282
330
|
debugLog(`[judgeMissionCompletion] Received response, parsing output_text`);
|
|
283
331
|
debugLog(`[judgeMissionCompletion] Raw output_text: ${response.output_text?.substring(0, 500)}`);
|
|
284
332
|
|
|
285
|
-
// Completion judge 자신의 응답은 히스토리에 추가하지 않음 (Orchestrator와 동일한 히스토리 유지)
|
|
286
|
-
// 대화 기록 초기화 (다음 판단을 위해)
|
|
287
|
-
debugLog(`[judgeMissionCompletion] Resetting completion judge conversation...`);
|
|
288
|
-
resetCompletionJudgeConversation();
|
|
289
|
-
|
|
290
333
|
try {
|
|
291
|
-
|
|
334
|
+
let outputText = response.output_text || '';
|
|
335
|
+
let judgment = null;
|
|
336
|
+
|
|
337
|
+
// 1차 시도: 전체 텍스트를 JSON으로 파싱
|
|
338
|
+
try {
|
|
339
|
+
judgment = JSON.parse(outputText);
|
|
340
|
+
debugLog(`[judgeMissionCompletion] Parsed as pure JSON`);
|
|
341
|
+
} catch (e) {
|
|
342
|
+
// 2차 시도: 텍스트 끝에 붙은 JSON 추출 (GLM 모델 대응)
|
|
343
|
+
// 패턴: ...텍스트...{"should_complete":true,"whatUserShouldSay":"..."}
|
|
344
|
+
const jsonMatch = outputText.match(/\{[^{}]*"should_complete"\s*:\s*(true|false)[^{}]*\}/);
|
|
345
|
+
if (jsonMatch) {
|
|
346
|
+
debugLog(`[judgeMissionCompletion] Extracted JSON from text: ${jsonMatch[0]}`);
|
|
347
|
+
judgment = JSON.parse(jsonMatch[0]);
|
|
348
|
+
} else {
|
|
349
|
+
// 3차 시도: 마지막 { } 블록 추출
|
|
350
|
+
const lastBraceStart = outputText.lastIndexOf('{');
|
|
351
|
+
const lastBraceEnd = outputText.lastIndexOf('}');
|
|
352
|
+
if (lastBraceStart !== -1 && lastBraceEnd > lastBraceStart) {
|
|
353
|
+
const jsonCandidate = outputText.substring(lastBraceStart, lastBraceEnd + 1);
|
|
354
|
+
debugLog(`[judgeMissionCompletion] Trying last JSON block: ${jsonCandidate}`);
|
|
355
|
+
judgment = JSON.parse(jsonCandidate);
|
|
356
|
+
} else {
|
|
357
|
+
throw new Error('No JSON found in output');
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
292
362
|
debugLog(`[judgeMissionCompletion] Parsed judgment successfully`);
|
|
293
363
|
debugLog(`[judgeMissionCompletion] should_complete: ${judgment.should_complete}`);
|
|
294
364
|
debugLog(`[judgeMissionCompletion] whatUserShouldSay: ${judgment.whatUserShouldSay}`);
|
|
@@ -308,17 +378,24 @@ export async function judgeMissionCompletion(templateVars = {}) {
|
|
|
308
378
|
} catch (parseError) {
|
|
309
379
|
debugLog(`[judgeMissionCompletion] Parse error: ${parseError.message}`);
|
|
310
380
|
debugLog(`[judgeMissionCompletion] Failed to parse output_text as JSON`);
|
|
311
|
-
|
|
381
|
+
debugLog(`[judgeMissionCompletion] Raw output_text: ${response.output_text}`);
|
|
382
|
+
|
|
383
|
+
// JSON 파싱 실패 시 안전하게 완료 처리 (무한 루프 방지)
|
|
384
|
+
// orchestrator가 이미 message만 반환한 시점이므로 완료로 처리
|
|
385
|
+
debugLog(`[judgeMissionCompletion] Defaulting to complete to prevent infinite loop`);
|
|
386
|
+
|
|
387
|
+
debugLog('========================================');
|
|
388
|
+
debugLog('=== judgeMissionCompletion END (PARSE_ERROR) =');
|
|
389
|
+
debugLog('========================================');
|
|
390
|
+
debugLog('');
|
|
391
|
+
|
|
392
|
+
return { shouldComplete: true, whatUserShouldSay: "" };
|
|
312
393
|
}
|
|
313
394
|
|
|
314
395
|
} catch (error) {
|
|
315
396
|
debugLog(`[judgeMissionCompletion] ERROR: ${error.message}, error.name: ${error.name}`);
|
|
316
397
|
debugLog(`[judgeMissionCompletion] Error stack: ${error.stack}`);
|
|
317
398
|
|
|
318
|
-
// 에러 발생 시 대화 기록 초기화
|
|
319
|
-
debugLog(`[judgeMissionCompletion] Resetting completion judge conversation due to error...`);
|
|
320
|
-
resetCompletionJudgeConversation();
|
|
321
|
-
|
|
322
399
|
// AbortError는 즉시 전파 (세션 중단)
|
|
323
400
|
if (error.name === 'AbortError') {
|
|
324
401
|
debugLog(`[judgeMissionCompletion] AbortError detected, propagating to caller`);
|
|
@@ -329,15 +406,15 @@ export async function judgeMissionCompletion(templateVars = {}) {
|
|
|
329
406
|
throw error;
|
|
330
407
|
}
|
|
331
408
|
|
|
332
|
-
// 다른 에러 발생 시 안전하게
|
|
333
|
-
debugLog(`[judgeMissionCompletion] Non-abort error, returning safe default (shouldComplete=
|
|
409
|
+
// 다른 에러 발생 시 안전하게 완료 처리 (무한 루프 방지)
|
|
410
|
+
debugLog(`[judgeMissionCompletion] Non-abort error, returning safe default (shouldComplete=true to prevent loop)`);
|
|
334
411
|
debugLog('========================================');
|
|
335
412
|
debugLog('=== judgeMissionCompletion END (ERROR) =');
|
|
336
413
|
debugLog('========================================');
|
|
337
414
|
debugLog('');
|
|
338
415
|
|
|
339
416
|
return {
|
|
340
|
-
shouldComplete:
|
|
417
|
+
shouldComplete: true,
|
|
341
418
|
whatUserShouldSay: ""
|
|
342
419
|
};
|
|
343
420
|
}
|