aiexecode 1.0.57 → 1.0.58

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.

@@ -0,0 +1,247 @@
1
+ import dotenv from "dotenv";
2
+ import { request, isContextWindowError, getModelForProvider } from "../system/ai_request.js";
3
+ import { getOrchestratorConversation } from "./orchestrator.js";
4
+ import { createSystemMessage } from "../util/prompt_loader.js";
5
+ import { createDebugLogger } from "../util/debug_log.js";
6
+
7
+ dotenv.config({ quiet: true });
8
+
9
+ const debugLog = createDebugLogger('completion_judge.log', 'completion_judge');
10
+
11
+ export const completionJudgmentSchema = {
12
+ name: "completion_judgment",
13
+ schema: {
14
+ type: "object",
15
+ properties: {
16
+ should_complete: {
17
+ type: "boolean",
18
+ description: "Whether the mission is truly complete (true) or should continue (false). If whatUserShouldSay is hard to determine, set this to true."
19
+ },
20
+ reason: {
21
+ type: "string",
22
+ description: "Brief explanation in 1-2 sentences for the decision",
23
+ minLength: 1
24
+ },
25
+ whatUserShouldSay: {
26
+ type: "string",
27
+ description: "The most appropriate thing the user should say next, as if you (the assistant) were the user observing the current situation. Put yourself in the user's shoes and determine what would be the most natural and helpful thing to say to guide the agent forward. MUST be a single, clear, decisive instruction without hedging words like 'maybe', 'perhaps', 'consider', 'you could', or offering multiple options. Pick the MOST important next step and state it with strong conviction. Empty string if mission is complete or if it's genuinely unclear what the user should say."
28
+ }
29
+ },
30
+ required: ["should_complete", "reason", "whatUserShouldSay"],
31
+ additionalProperties: false
32
+ },
33
+ strict: true
34
+ };
35
+
36
+ // systemMessage는 judgeMissionCompletion 호출 시 동적으로 생성됨
37
+
38
+ const completionJudgeConversation = [];
39
+ let lastOrchestratorSnapshotLength = 0;
40
+
41
+ function cloneMessage(message) {
42
+ return JSON.parse(JSON.stringify(message));
43
+ }
44
+
45
+
46
+ async function createCompletionJudgeRequestOptions() {
47
+ const model = await getModelForProvider();
48
+ return {
49
+ taskName: 'completion_judge',
50
+ model,
51
+ isGpt5Model: model.startsWith("gpt-5")
52
+ };
53
+ }
54
+
55
+ /**
56
+ * Completion judge conversation을 초기화하거나 시스템 프롬프트를 업데이트합니다.
57
+ * 매 요청마다 프롬프트 파일을 새로 읽어서 변경사항을 즉시 반영합니다.
58
+ */
59
+ async function ensureCompletionJudgeConversationInitialized(templateVars = {}) {
60
+ // 매번 최신 시스템 프롬프트를 로드
61
+ const systemMessage = await createSystemMessage("completion_judge.txt", templateVars);
62
+
63
+ const systemMessageEntry = {
64
+ role: "system",
65
+ content: [
66
+ {
67
+ type: "input_text",
68
+ text: systemMessage.content
69
+ }
70
+ ]
71
+ };
72
+
73
+ // conversation이 비어있으면 새로 추가
74
+ if (!completionJudgeConversation.length) {
75
+ completionJudgeConversation.push(systemMessageEntry);
76
+ } else {
77
+ // conversation이 있으면 첫 번째 system 메시지를 업데이트
78
+ if (completionJudgeConversation[0]?.role === "system") {
79
+ completionJudgeConversation[0] = systemMessageEntry;
80
+ } else {
81
+ // system 메시지가 맨 앞에 없으면 추가
82
+ completionJudgeConversation.unshift(systemMessageEntry);
83
+ }
84
+ }
85
+ }
86
+
87
+ function syncOrchestratorConversation() {
88
+ const sourceConversation = getOrchestratorConversation();
89
+ if (!Array.isArray(sourceConversation) || !sourceConversation.length) {
90
+ return;
91
+ }
92
+
93
+ for (let i = lastOrchestratorSnapshotLength; i < sourceConversation.length; i++) {
94
+ const entry = sourceConversation[i];
95
+ if (!entry) continue;
96
+
97
+ // Completion judge는 자체 system 메시지를 유지하므로, Orchestrator의 system 메시지는 제외
98
+ if (entry.role === "system") continue;
99
+
100
+ // reasoning과 message를 모두 그대로 복사
101
+ // ai_request.js에서 이미 reasoning-message 쌍이 유지되고 있음
102
+ completionJudgeConversation.push(cloneMessage(entry));
103
+ }
104
+
105
+ lastOrchestratorSnapshotLength = sourceConversation.length;
106
+ }
107
+
108
+ function trimCompletionJudgeConversation() {
109
+ for (let i = 1; i < completionJudgeConversation.length - 1; i++) {
110
+ const candidate = completionJudgeConversation[i];
111
+ if (candidate?.role !== "system") {
112
+ completionJudgeConversation.splice(i, 1);
113
+ return true;
114
+ }
115
+ }
116
+ return false;
117
+ }
118
+
119
+ async function dispatchCompletionJudgeRequest(options) {
120
+ if (!options) {
121
+ throw new Error('Completion judge request options not initialized.');
122
+ }
123
+
124
+ while (true) {
125
+ const { model, isGpt5Model, taskName } = options;
126
+ const requestPayload = {
127
+ model,
128
+ input: completionJudgeConversation,
129
+ text: {
130
+ format: {
131
+ type: "json_schema",
132
+ name: completionJudgmentSchema.name,
133
+ strict: completionJudgmentSchema.strict,
134
+ schema: completionJudgmentSchema.schema
135
+ }
136
+ },
137
+ reasoning: {},
138
+ tool_choice: "none",
139
+ tools: [],
140
+ top_p: 1,
141
+ store: true
142
+ };
143
+
144
+ if (!isGpt5Model) {
145
+ requestPayload.temperature = 0;
146
+ }
147
+
148
+ try {
149
+ return await request(taskName, requestPayload);
150
+ } catch (error) {
151
+ // AbortError는 즉시 전파 (세션 중단)
152
+ if (error.name === 'AbortError') {
153
+ debugLog(`[dispatchCompletionJudgeRequest] Request aborted by user`);
154
+ throw error;
155
+ }
156
+
157
+ if (!isContextWindowError(error)) {
158
+ throw error;
159
+ }
160
+
161
+ const trimmed = trimCompletionJudgeConversation();
162
+ if (!trimmed) {
163
+ throw error;
164
+ }
165
+ }
166
+ }
167
+ }
168
+
169
+ export function resetCompletionJudgeConversation() {
170
+ completionJudgeConversation.length = 0;
171
+ lastOrchestratorSnapshotLength = 0;
172
+ }
173
+
174
+ export function getCompletionJudgeConversation() {
175
+ return completionJudgeConversation;
176
+ }
177
+
178
+ export function restoreCompletionJudgeConversation(savedConversation, savedSnapshotLength = 0) {
179
+ completionJudgeConversation.length = 0;
180
+ if (Array.isArray(savedConversation)) {
181
+ completionJudgeConversation.push(...savedConversation);
182
+ }
183
+ lastOrchestratorSnapshotLength = savedSnapshotLength;
184
+ }
185
+
186
+ /**
187
+ * Orchestrator가 function call 없이 message만 반환했을 때,
188
+ * 실제로 미션이 완료되었는지 판단하는 함수
189
+ *
190
+ * @param {Object} templateVars - 시스템 프롬프트 템플릿 변수 (예: mission)
191
+ * @returns {Promise<{shouldComplete: boolean, reason: string}>}
192
+ */
193
+ export async function judgeMissionCompletion(templateVars = {}) {
194
+ debugLog(`[judgeMissionCompletion] Called with templateVars: ${JSON.stringify(Object.keys(templateVars))}`);
195
+
196
+ try {
197
+ const requestOptions = await createCompletionJudgeRequestOptions();
198
+
199
+ // Completion judge 자체 system 메시지 초기화 (템플릿 변수 전달)
200
+ await ensureCompletionJudgeConversationInitialized(templateVars);
201
+ // Orchestrator의 대화 내용 동기화 (system 메시지 제외)
202
+ syncOrchestratorConversation();
203
+
204
+ debugLog(`[judgeMissionCompletion] Sending request with ${completionJudgeConversation.length} conversation entries`);
205
+
206
+ const response = await dispatchCompletionJudgeRequest(requestOptions);
207
+
208
+ debugLog(`[judgeMissionCompletion] Received response, parsing output_text`);
209
+
210
+ // Completion judge 자신의 응답은 히스토리에 추가하지 않음 (Orchestrator와 동일한 히스토리 유지)
211
+ // 대화 기록 초기화 (다음 판단을 위해)
212
+ resetCompletionJudgeConversation();
213
+
214
+ try {
215
+ const judgment = JSON.parse(response.output_text);
216
+ debugLog(`[judgeMissionCompletion] Parsed judgment: ${JSON.stringify(judgment)}`);
217
+
218
+ return {
219
+ shouldComplete: judgment.should_complete === true,
220
+ reason: judgment.reason || "No reason provided",
221
+ whatUserShouldSay: judgment.whatUserShouldSay || ""
222
+ };
223
+ } catch (parseError) {
224
+ debugLog(`[judgeMissionCompletion] Parse error: ${parseError.message}`);
225
+ throw new Error('Completion judge response did not include valid JSON output_text.');
226
+ }
227
+
228
+ } catch (error) {
229
+ debugLog(`[judgeMissionCompletion] ERROR: ${error.message}, error.name: ${error.name}`);
230
+
231
+ // 에러 발생 시 대화 기록 초기화
232
+ resetCompletionJudgeConversation();
233
+
234
+ // AbortError는 즉시 전파 (세션 중단)
235
+ if (error.name === 'AbortError') {
236
+ debugLog(`[judgeMissionCompletion] AbortError detected, propagating to caller`);
237
+ throw error;
238
+ }
239
+
240
+ // 다른 에러 발생 시 안전하게 계속 진행
241
+ return {
242
+ shouldComplete: false,
243
+ reason: `Error during judgment: ${error.message} - continuing to be safe`,
244
+ whatUserShouldSay: ""
245
+ };
246
+ }
247
+ }
@@ -351,10 +351,16 @@ export async function continueOrchestratorConversation() {
351
351
 
352
352
 
353
353
  // 탐색 기반으로 현재 상황을 분석하고, 다음에 취할 행동을 AI에게 결정받습니다.
354
- export async function orchestrateMission({ improvement_points = '', mcpToolSchemas = [] }) {
354
+ export async function orchestrateMission({ improvement_points = '', mcpToolSchemas = [], isAutoGenerated = false }) {
355
355
  const taskName = 'orchestrator';
356
356
 
357
+ debugLog(`[orchestrateMission] Called with improvement_points: "${improvement_points?.substring(0, 100) || '(empty)'}", isAutoGenerated: ${isAutoGenerated}`);
357
358
  const improvementPointsText = typeof improvement_points === 'string' && improvement_points.trim().length ? improvement_points : '';
359
+ debugLog(`[orchestrateMission] improvementPointsText after processing: "${improvementPointsText.substring(0, 100) || '(empty)'}"`);
360
+ if (isAutoGenerated) {
361
+ debugLog(`[orchestrateMission] This is an auto-generated user message (from completion_judge)`);
362
+ }
363
+
358
364
 
359
365
  // Python 사용 가능 여부 확인
360
366
  const hasPython = process.app_custom?.systemInfo?.commands?.hasPython || false;
@@ -411,8 +417,8 @@ export async function orchestrateMission({ improvement_points = '', mcpToolSchem
411
417
 
412
418
  await ensureConversationInitialized();
413
419
 
414
-
415
- orchestratorConversation.push({
420
+ debugLog(`[orchestrateMission] Adding user message to conversation: "${improvementPointsText.substring(0, 100)}"`);
421
+ const userMessage = {
416
422
  role: "user",
417
423
  content: [
418
424
  {
@@ -420,9 +426,19 @@ export async function orchestrateMission({ improvement_points = '', mcpToolSchem
420
426
  text: improvementPointsText
421
427
  }
422
428
  ]
423
- });
429
+ };
430
+
431
+ // Auto-generated user message인 경우 _internal_only 플래그 추가
432
+ if (isAutoGenerated) {
433
+ userMessage._internal_only = true;
434
+ debugLog(`[_internal_only] Marked user message as internal-only (auto-generated from completion_judge)`);
435
+ }
436
+
437
+ orchestratorConversation.push(userMessage);
438
+ debugLog(`[orchestrateMission] Conversation length after adding user message: ${orchestratorConversation.length}`);
424
439
 
425
440
  const response = await dispatchOrchestratorRequest({ toolChoice: "required" });
426
441
  appendResponseToConversation(response);
442
+ debugLog(`[orchestrateMission] Conversation length after response: ${orchestratorConversation.length}`);
427
443
  return response;
428
444
  }
@@ -494,6 +494,15 @@ export async function request(taskName, requestPayload) {
494
494
  // requestPayload를 deep copy하여 원본 보호
495
495
  let payloadCopy = JSON.parse(JSON.stringify(requestPayload));
496
496
 
497
+ // _internal_only 속성 제거 (API 요청에 무효한 속성)
498
+ if (payloadCopy.input && Array.isArray(payloadCopy.input)) {
499
+ for (const msg of payloadCopy.input) {
500
+ if (msg._internal_only) {
501
+ delete msg._internal_only;
502
+ }
503
+ }
504
+ }
505
+
497
506
  // function_call_output의 output에서 original_result 제거
498
507
  if (false && payloadCopy.input && Array.isArray(payloadCopy.input)) {
499
508
  for (const msg of payloadCopy.input) {