triflux 3.3.0-dev.3 → 3.3.0-dev.6
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/hub/assign-callbacks.mjs +136 -0
- package/hub/bridge.mjs +289 -98
- package/hub/pipe.mjs +81 -0
- package/hub/server.mjs +159 -133
- package/hub/store.mjs +36 -2
- package/hub/team/cli-team-status.mjs +17 -3
- package/hub/team/native-supervisor.mjs +62 -22
- package/hub/team/native.mjs +266 -200
- package/hub/team/nativeProxy.mjs +173 -72
- package/hub/tools.mjs +6 -6
- package/hub/workers/delegator-mcp.mjs +285 -140
- package/package.json +60 -60
- package/scripts/completions/tfx.bash +47 -0
- package/scripts/completions/tfx.fish +44 -0
- package/scripts/completions/tfx.zsh +83 -0
- package/scripts/lib/mcp-filter.mjs +642 -0
- package/scripts/lib/mcp-server-catalog.mjs +118 -0
- package/scripts/mcp-check.mjs +126 -88
- package/scripts/test-tfx-route-no-claude-native.mjs +10 -2
- package/scripts/tfx-route.sh +504 -180
- package/skills/tfx-auto/SKILL.md +6 -1
|
@@ -14,6 +14,13 @@ import * as z from 'zod';
|
|
|
14
14
|
|
|
15
15
|
import { CodexMcpWorker } from './codex-mcp.mjs';
|
|
16
16
|
import { GeminiWorker } from './gemini-worker.mjs';
|
|
17
|
+
import {
|
|
18
|
+
buildPromptHint,
|
|
19
|
+
getCodexMcpConfig,
|
|
20
|
+
getGeminiAllowedServers,
|
|
21
|
+
resolveMcpProfile,
|
|
22
|
+
SUPPORTED_MCP_PROFILES,
|
|
23
|
+
} from '../../scripts/lib/mcp-filter.mjs';
|
|
17
24
|
|
|
18
25
|
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
|
|
19
26
|
const SERVER_INFO = { name: 'triflux-delegator', version: '1.0.0' };
|
|
@@ -78,12 +85,6 @@ const REVIEW_INSTRUCTION_BY_AGENT = Object.freeze({
|
|
|
78
85
|
'quality-reviewer': '품질 리뷰 모드로 동작하라. 로직 결함, 유지보수성 저하, 테스트 누락을 우선 식별하라.',
|
|
79
86
|
});
|
|
80
87
|
|
|
81
|
-
const IMPLEMENT_AGENT_SET = new Set(['executor', 'build-fixer', 'debugger', 'deep-executor']);
|
|
82
|
-
const ANALYZE_AGENT_SET = new Set(['architect', 'planner', 'critic', 'analyst', 'scientist', 'scientist-deep', 'document-specialist']);
|
|
83
|
-
const REVIEW_AGENT_SET = new Set(['code-reviewer', 'security-reviewer', 'quality-reviewer', 'verifier']);
|
|
84
|
-
const DOCS_AGENT_SET = new Set(['designer', 'writer']);
|
|
85
|
-
const SEARCH_TOOL_ORDER = ['brave-search', 'tavily', 'exa'];
|
|
86
|
-
|
|
87
88
|
function cloneEnv(env = process.env) {
|
|
88
89
|
return Object.fromEntries(
|
|
89
90
|
Object.entries(env).filter(([, value]) => typeof value === 'string'),
|
|
@@ -125,73 +126,6 @@ function resolveRouteScript(explicitPath, cwd = process.cwd()) {
|
|
|
125
126
|
return null;
|
|
126
127
|
}
|
|
127
128
|
|
|
128
|
-
function resolveMcpProfile(agentType, requested = 'auto') {
|
|
129
|
-
if (requested && requested !== 'auto') return requested;
|
|
130
|
-
if (IMPLEMENT_AGENT_SET.has(agentType)) return 'implement';
|
|
131
|
-
if (ANALYZE_AGENT_SET.has(agentType)) return 'analyze';
|
|
132
|
-
if (REVIEW_AGENT_SET.has(agentType)) return 'review';
|
|
133
|
-
if (DOCS_AGENT_SET.has(agentType)) return 'docs';
|
|
134
|
-
return 'minimal';
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function resolveSearchToolOrder(searchTool, workerIndex) {
|
|
138
|
-
const available = [...SEARCH_TOOL_ORDER];
|
|
139
|
-
if (searchTool && available.includes(searchTool)) {
|
|
140
|
-
return [searchTool, ...available.filter((tool) => tool !== searchTool)];
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (Number.isInteger(workerIndex) && workerIndex > 0 && available.length > 1) {
|
|
144
|
-
const offset = (workerIndex - 1) % available.length;
|
|
145
|
-
return available.slice(offset).concat(available.slice(0, offset));
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return available;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function buildPromptHint(profile, args) {
|
|
152
|
-
const orderedTools = resolveSearchToolOrder(args.searchTool, args.workerIndex);
|
|
153
|
-
switch (profile) {
|
|
154
|
-
case 'implement':
|
|
155
|
-
return [
|
|
156
|
-
'context7으로 라이브러리 문서를 조회하세요.',
|
|
157
|
-
`웹 검색은 ${orderedTools[0]}를 사용하세요.`,
|
|
158
|
-
'검색 도구 실패 시 402, 429, 432, 433, quota 에러에서 재시도하지 말고 다음 도구로 전환하세요.',
|
|
159
|
-
].join(' ');
|
|
160
|
-
case 'analyze':
|
|
161
|
-
return [
|
|
162
|
-
'context7으로 관련 문서를 조회하세요.',
|
|
163
|
-
`웹 검색 우선순위: ${orderedTools.join(', ')}. 402, 429, 432, 433, quota 에러 시 즉시 다음 도구로 전환.`,
|
|
164
|
-
'모든 검색 실패 시 playwright로 직접 방문 (최대 3 URL).',
|
|
165
|
-
'검색 깊이를 제한하고 결과를 빠르게 요약하세요.',
|
|
166
|
-
].join(' ');
|
|
167
|
-
case 'review':
|
|
168
|
-
return 'sequential-thinking으로 체계적으로 분석하세요.';
|
|
169
|
-
case 'docs':
|
|
170
|
-
return [
|
|
171
|
-
'context7으로 공식 문서를 참조하세요.',
|
|
172
|
-
`추가 검색은 ${orderedTools[0]}를 사용하세요.`,
|
|
173
|
-
'검색 결과의 출처 URL을 함께 제시하세요.',
|
|
174
|
-
].join(' ');
|
|
175
|
-
default:
|
|
176
|
-
return '';
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function resolveGeminiMcpServers(profile) {
|
|
181
|
-
switch (profile) {
|
|
182
|
-
case 'implement':
|
|
183
|
-
return ['context7', 'brave-search'];
|
|
184
|
-
case 'analyze':
|
|
185
|
-
return ['context7', 'brave-search', 'exa', 'tavily'];
|
|
186
|
-
case 'review':
|
|
187
|
-
return ['sequential-thinking'];
|
|
188
|
-
case 'docs':
|
|
189
|
-
return ['context7', 'brave-search'];
|
|
190
|
-
default:
|
|
191
|
-
return [];
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
129
|
function resolveCodexProfile(agentType) {
|
|
196
130
|
return CODEX_PROFILE_BY_AGENT[agentType] || 'high';
|
|
197
131
|
}
|
|
@@ -232,10 +166,16 @@ function withContext(prompt, contextFile) {
|
|
|
232
166
|
}
|
|
233
167
|
|
|
234
168
|
function withPromptHint(prompt, args) {
|
|
235
|
-
const
|
|
236
|
-
const hint = buildPromptHint(
|
|
237
|
-
|
|
238
|
-
|
|
169
|
+
const promptWithContext = withContext(prompt, args.contextFile);
|
|
170
|
+
const hint = buildPromptHint({
|
|
171
|
+
agentType: args.agentType,
|
|
172
|
+
requestedProfile: args.mcpProfile,
|
|
173
|
+
searchTool: args.searchTool,
|
|
174
|
+
workerIndex: Number.isInteger(args.workerIndex) ? args.workerIndex : undefined,
|
|
175
|
+
taskText: promptWithContext,
|
|
176
|
+
});
|
|
177
|
+
if (!hint) return promptWithContext;
|
|
178
|
+
return `${promptWithContext}. ${hint}`;
|
|
239
179
|
}
|
|
240
180
|
|
|
241
181
|
function joinInstructions(...values) {
|
|
@@ -287,7 +227,7 @@ const DelegateInputSchema = z.object({
|
|
|
287
227
|
timeoutMs: z.number().int().positive().optional().describe('요청 타임아웃(ms)'),
|
|
288
228
|
sessionKey: z.string().optional().describe('Codex warm session 재사용 키'),
|
|
289
229
|
resetSession: z.boolean().optional().describe('기존 Codex 세션 초기화 여부'),
|
|
290
|
-
mcpProfile: z.enum(
|
|
230
|
+
mcpProfile: z.enum(SUPPORTED_MCP_PROFILES).default('auto'),
|
|
291
231
|
contextFile: z.string().optional().describe('tfx-route prior_context 파일 경로'),
|
|
292
232
|
searchTool: z.enum(['brave-search', 'tavily', 'exa']).optional().describe('검색 우선 도구'),
|
|
293
233
|
workerIndex: z.number().int().positive().optional().describe('병렬 워커 인덱스'),
|
|
@@ -308,9 +248,16 @@ const DelegateStatusInputSchema = z.object({
|
|
|
308
248
|
jobId: z.string().min(1).describe('조회할 비동기 job ID'),
|
|
309
249
|
});
|
|
310
250
|
|
|
251
|
+
const DelegateReplyInputSchema = z.object({
|
|
252
|
+
job_id: z.string().min(1).describe('후속 응답을 보낼 기존 delegate job ID'),
|
|
253
|
+
reply: z.string().min(1).describe('후속 사용자 응답'),
|
|
254
|
+
done: z.boolean().default(false).describe('true이면 응답 처리 후 대화를 종료'),
|
|
255
|
+
});
|
|
256
|
+
|
|
311
257
|
const DelegateOutputSchema = z.object({
|
|
312
258
|
ok: z.boolean(),
|
|
313
259
|
jobId: z.string().optional(),
|
|
260
|
+
job_id: z.string().optional(),
|
|
314
261
|
mode: z.enum(['sync', 'async']).optional(),
|
|
315
262
|
status: z.enum(['running', 'completed', 'failed']).optional(),
|
|
316
263
|
error: z.string().optional(),
|
|
@@ -327,6 +274,7 @@ const DelegateOutputSchema = z.object({
|
|
|
327
274
|
stderr: z.string().optional(),
|
|
328
275
|
threadId: z.string().nullable().optional(),
|
|
329
276
|
sessionKey: z.string().nullable().optional(),
|
|
277
|
+
conversationOpen: z.boolean().optional(),
|
|
330
278
|
});
|
|
331
279
|
|
|
332
280
|
function isTeamRouteRequested(args) {
|
|
@@ -343,6 +291,44 @@ function pickRouteMode(provider) {
|
|
|
343
291
|
return provider === 'auto' ? 'auto' : provider;
|
|
344
292
|
}
|
|
345
293
|
|
|
294
|
+
function sanitizeDelegateArgs(args = {}) {
|
|
295
|
+
return {
|
|
296
|
+
provider: args.provider || 'auto',
|
|
297
|
+
agentType: args.agentType || 'executor',
|
|
298
|
+
cwd: args.cwd || null,
|
|
299
|
+
timeoutMs: Number.isFinite(Number(args.timeoutMs)) ? Math.trunc(Number(args.timeoutMs)) : null,
|
|
300
|
+
sessionKey: args.sessionKey || null,
|
|
301
|
+
resetSession: Boolean(args.resetSession),
|
|
302
|
+
mcpProfile: args.mcpProfile || 'auto',
|
|
303
|
+
contextFile: args.contextFile || null,
|
|
304
|
+
searchTool: args.searchTool || null,
|
|
305
|
+
workerIndex: Number.isInteger(args.workerIndex) ? args.workerIndex : null,
|
|
306
|
+
model: args.model || null,
|
|
307
|
+
developerInstructions: args.developerInstructions || null,
|
|
308
|
+
compactPrompt: args.compactPrompt || null,
|
|
309
|
+
threadId: args.threadId || null,
|
|
310
|
+
codexTransport: args.codexTransport || null,
|
|
311
|
+
noClaudeNative: args.noClaudeNative === true,
|
|
312
|
+
teamName: args.teamName || null,
|
|
313
|
+
teamTaskId: args.teamTaskId || null,
|
|
314
|
+
teamAgentName: args.teamAgentName || null,
|
|
315
|
+
teamLeadName: args.teamLeadName || null,
|
|
316
|
+
hubUrl: args.hubUrl || null,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function formatConversationTranscript(turns = []) {
|
|
321
|
+
return turns.map((turn, index) => {
|
|
322
|
+
const parts = [
|
|
323
|
+
`Turn ${index + 1} user:\n${turn.user}`,
|
|
324
|
+
];
|
|
325
|
+
if (typeof turn.assistant === 'string' && turn.assistant.trim()) {
|
|
326
|
+
parts.push(`Turn ${index + 1} assistant:\n${turn.assistant}`);
|
|
327
|
+
}
|
|
328
|
+
return parts.join('\n\n');
|
|
329
|
+
}).join('\n\n');
|
|
330
|
+
}
|
|
331
|
+
|
|
346
332
|
async function emitProgress(extra, progress, total, message) {
|
|
347
333
|
if (extra?._meta?.progressToken === undefined) return;
|
|
348
334
|
await extra.sendNotification({
|
|
@@ -389,6 +375,7 @@ export class DelegatorMcpWorker {
|
|
|
389
375
|
this.server = null;
|
|
390
376
|
this.transport = null;
|
|
391
377
|
this.jobs = new Map();
|
|
378
|
+
this.geminiConversations = new Map();
|
|
392
379
|
this.routeChildren = new Set();
|
|
393
380
|
this.ready = false;
|
|
394
381
|
}
|
|
@@ -425,6 +412,15 @@ export class DelegatorMcpWorker {
|
|
|
425
412
|
return createToolResponse(payload, { isError: payload.ok === false });
|
|
426
413
|
});
|
|
427
414
|
|
|
415
|
+
server.registerTool('triflux-delegate-reply', {
|
|
416
|
+
description: '기존 delegate job에 후속 응답을 보내고, Gemini direct job이면 multi-turn 대화를 이어갑니다.',
|
|
417
|
+
inputSchema: DelegateReplyInputSchema,
|
|
418
|
+
outputSchema: DelegateOutputSchema,
|
|
419
|
+
}, async (args, extra) => {
|
|
420
|
+
const payload = await this.reply(args, extra);
|
|
421
|
+
return createToolResponse(payload, { isError: payload.ok === false });
|
|
422
|
+
});
|
|
423
|
+
|
|
428
424
|
this.server = server;
|
|
429
425
|
this.ready = true;
|
|
430
426
|
}
|
|
@@ -453,6 +449,7 @@ export class DelegatorMcpWorker {
|
|
|
453
449
|
job.worker = null;
|
|
454
450
|
}
|
|
455
451
|
}
|
|
452
|
+
this.geminiConversations.clear();
|
|
456
453
|
|
|
457
454
|
if (this.server) {
|
|
458
455
|
await this.server.close().catch(() => {});
|
|
@@ -481,7 +478,7 @@ export class DelegatorMcpWorker {
|
|
|
481
478
|
if (args.mode === 'async') {
|
|
482
479
|
return this._startAsyncJob(args, extra);
|
|
483
480
|
}
|
|
484
|
-
return this.
|
|
481
|
+
return this._runSyncJob(args, extra);
|
|
485
482
|
}
|
|
486
483
|
|
|
487
484
|
async getJobStatus(jobId, extra) {
|
|
@@ -501,6 +498,85 @@ export class DelegatorMcpWorker {
|
|
|
501
498
|
return payload;
|
|
502
499
|
}
|
|
503
500
|
|
|
501
|
+
async reply({ job_id, reply, done = false }, extra) {
|
|
502
|
+
const job = this.jobs.get(job_id);
|
|
503
|
+
if (!job) {
|
|
504
|
+
return createErrorPayload(`알 수 없는 jobId: ${job_id}`, { jobId: job_id, job_id });
|
|
505
|
+
}
|
|
506
|
+
if (job.status === 'running') {
|
|
507
|
+
return createErrorPayload(`job ${job_id}가 아직 실행 중입니다.`, { jobId: job_id, job_id });
|
|
508
|
+
}
|
|
509
|
+
if (job.providerRequested !== 'gemini' || job.transport !== 'gemini-worker') {
|
|
510
|
+
return createErrorPayload('delegate-reply는 현재 direct Gemini job에만 지원됩니다.', {
|
|
511
|
+
jobId: job_id,
|
|
512
|
+
job_id,
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const conversation = this.geminiConversations.get(job_id);
|
|
517
|
+
if (!conversation) {
|
|
518
|
+
return createErrorPayload(`Gemini 대화 컨텍스트가 없습니다: ${job_id}`, { jobId: job_id, job_id });
|
|
519
|
+
}
|
|
520
|
+
if (conversation.closed) {
|
|
521
|
+
return createErrorPayload(`이미 종료된 대화입니다: ${job_id}`, { jobId: job_id, job_id });
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
await emitProgress(extra, DIRECT_PROGRESS_START, 100, `job ${job_id} 후속 응답을 시작합니다.`);
|
|
525
|
+
job.status = 'running';
|
|
526
|
+
job.updatedAt = new Date().toISOString();
|
|
527
|
+
|
|
528
|
+
const worker = this._createGeminiWorker();
|
|
529
|
+
job.worker = worker;
|
|
530
|
+
const prompt = this._buildGeminiReplyPrompt(conversation, reply);
|
|
531
|
+
|
|
532
|
+
try {
|
|
533
|
+
const result = await worker.execute(prompt, {
|
|
534
|
+
cwd: job.requestArgs.cwd || this.cwd,
|
|
535
|
+
timeoutMs: resolveTimeoutMs(job.agentType, job.requestArgs.timeoutMs),
|
|
536
|
+
model: job.requestArgs.model || resolveGeminiModel(job.agentType),
|
|
537
|
+
approvalMode: 'yolo',
|
|
538
|
+
allowedMcpServerNames: getGeminiAllowedServers(this._getMcpPolicyOptions(job.requestArgs)),
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
conversation.turns.push({
|
|
542
|
+
user: reply,
|
|
543
|
+
assistant: result.output,
|
|
544
|
+
});
|
|
545
|
+
conversation.updatedAt = new Date().toISOString();
|
|
546
|
+
conversation.closed = Boolean(done);
|
|
547
|
+
if (done) {
|
|
548
|
+
this.geminiConversations.delete(job_id);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
this._applyJobResult(job, {
|
|
552
|
+
ok: result.exitCode === 0,
|
|
553
|
+
status: result.exitCode === 0 ? 'completed' : 'failed',
|
|
554
|
+
providerRequested: 'gemini',
|
|
555
|
+
providerResolved: 'gemini',
|
|
556
|
+
agentType: job.agentType,
|
|
557
|
+
transport: 'gemini-worker',
|
|
558
|
+
exitCode: result.exitCode,
|
|
559
|
+
output: result.output,
|
|
560
|
+
sessionKey: result.sessionKey || job.sessionKey || null,
|
|
561
|
+
});
|
|
562
|
+
await emitProgress(extra, DIRECT_PROGRESS_DONE, 100, `job ${job_id} 후속 응답이 완료되었습니다.`);
|
|
563
|
+
return this._serializeJob(job);
|
|
564
|
+
} catch (error) {
|
|
565
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
566
|
+
this._applyJobResult(job, createErrorPayload(message, {
|
|
567
|
+
mode: job.mode,
|
|
568
|
+
providerRequested: 'gemini',
|
|
569
|
+
providerResolved: 'gemini',
|
|
570
|
+
agentType: job.agentType,
|
|
571
|
+
transport: 'gemini-worker',
|
|
572
|
+
}));
|
|
573
|
+
return this._serializeJob(job);
|
|
574
|
+
} finally {
|
|
575
|
+
await worker.stop().catch(() => {});
|
|
576
|
+
job.worker = null;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
504
580
|
_createGeminiWorker() {
|
|
505
581
|
return new GeminiWorker({
|
|
506
582
|
command: this.geminiCommand,
|
|
@@ -524,13 +600,33 @@ export class DelegatorMcpWorker {
|
|
|
524
600
|
});
|
|
525
601
|
}
|
|
526
602
|
|
|
527
|
-
|
|
528
|
-
|
|
603
|
+
_buildGeminiReplyPrompt(conversation, reply) {
|
|
604
|
+
const transcript = formatConversationTranscript(conversation.turns);
|
|
605
|
+
return [
|
|
606
|
+
'Continue the conversation using the prior transcript below.',
|
|
607
|
+
'',
|
|
608
|
+
'<conversation_history>',
|
|
609
|
+
transcript,
|
|
610
|
+
'</conversation_history>',
|
|
611
|
+
'',
|
|
612
|
+
'<latest_user_reply>',
|
|
613
|
+
reply,
|
|
614
|
+
'</latest_user_reply>',
|
|
615
|
+
].join('\n');
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
_getMcpPolicyOptions(args) {
|
|
619
|
+
return {
|
|
529
620
|
agentType: args.agentType || 'executor',
|
|
530
|
-
|
|
621
|
+
requestedProfile: args.mcpProfile || 'auto',
|
|
531
622
|
searchTool: args.searchTool,
|
|
532
623
|
workerIndex: Number.isInteger(args.workerIndex) ? args.workerIndex : undefined,
|
|
533
|
-
|
|
624
|
+
taskText: withContext(String(args.prompt ?? ''), args.contextFile),
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
_buildPromptHintInstruction(args) {
|
|
629
|
+
return buildPromptHint(this._getMcpPolicyOptions(args));
|
|
534
630
|
}
|
|
535
631
|
|
|
536
632
|
_shouldUseRoute(args) {
|
|
@@ -578,6 +674,7 @@ export class DelegatorMcpWorker {
|
|
|
578
674
|
this._buildPromptHintInstruction(args),
|
|
579
675
|
args.developerInstructions,
|
|
580
676
|
),
|
|
677
|
+
config: getCodexMcpConfig(this._getMcpPolicyOptions(args)),
|
|
581
678
|
compactPrompt: args.compactPrompt,
|
|
582
679
|
model: args.model,
|
|
583
680
|
});
|
|
@@ -599,15 +696,14 @@ export class DelegatorMcpWorker {
|
|
|
599
696
|
|
|
600
697
|
if (args.provider === 'gemini') {
|
|
601
698
|
const worker = this._createGeminiWorker();
|
|
699
|
+
const prompt = this._buildDirectPromptWithHint(args);
|
|
602
700
|
try {
|
|
603
|
-
const result = await worker.execute(
|
|
701
|
+
const result = await worker.execute(prompt, {
|
|
604
702
|
cwd: args.cwd || this.cwd,
|
|
605
703
|
timeoutMs: resolveTimeoutMs(args.agentType, args.timeoutMs),
|
|
606
704
|
model: args.model || resolveGeminiModel(args.agentType),
|
|
607
705
|
approvalMode: 'yolo',
|
|
608
|
-
allowedMcpServerNames:
|
|
609
|
-
resolveMcpProfile(args.agentType, args.mcpProfile),
|
|
610
|
-
),
|
|
706
|
+
allowedMcpServerNames: getGeminiAllowedServers(this._getMcpPolicyOptions(args)),
|
|
611
707
|
});
|
|
612
708
|
|
|
613
709
|
return {
|
|
@@ -621,6 +717,7 @@ export class DelegatorMcpWorker {
|
|
|
621
717
|
exitCode: result.exitCode,
|
|
622
718
|
output: result.output,
|
|
623
719
|
sessionKey: result.sessionKey,
|
|
720
|
+
_geminiPrompt: prompt,
|
|
624
721
|
};
|
|
625
722
|
} finally {
|
|
626
723
|
await worker.stop().catch(() => {});
|
|
@@ -662,61 +759,27 @@ export class DelegatorMcpWorker {
|
|
|
662
759
|
}
|
|
663
760
|
|
|
664
761
|
async _startAsyncJob(args, extra) {
|
|
665
|
-
const
|
|
666
|
-
|
|
667
|
-
const payload = {
|
|
668
|
-
ok: true,
|
|
669
|
-
jobId,
|
|
670
|
-
mode: 'async',
|
|
671
|
-
status: 'running',
|
|
672
|
-
providerRequested: args.provider,
|
|
673
|
-
providerResolved: null,
|
|
674
|
-
agentType: args.agentType,
|
|
675
|
-
transport: this._shouldUseRoute(args) ? 'route-script' : `${args.provider}-worker`,
|
|
676
|
-
createdAt: startedAt,
|
|
677
|
-
startedAt,
|
|
678
|
-
};
|
|
679
|
-
|
|
680
|
-
const job = {
|
|
681
|
-
...payload,
|
|
682
|
-
updatedAt: startedAt,
|
|
683
|
-
completedAt: null,
|
|
684
|
-
output: '',
|
|
685
|
-
stderr: '',
|
|
686
|
-
exitCode: null,
|
|
687
|
-
threadId: null,
|
|
688
|
-
sessionKey: args.sessionKey || null,
|
|
689
|
-
worker: null,
|
|
690
|
-
child: null,
|
|
691
|
-
};
|
|
692
|
-
this.jobs.set(jobId, job);
|
|
762
|
+
const job = this._createJob(args, 'async');
|
|
763
|
+
this.jobs.set(job.jobId, job);
|
|
693
764
|
|
|
694
|
-
await emitProgress(extra, DIRECT_PROGRESS_START, 100, `비동기 job ${jobId}를 시작합니다.`);
|
|
765
|
+
await emitProgress(extra, DIRECT_PROGRESS_START, 100, `비동기 job ${job.jobId}를 시작합니다.`);
|
|
695
766
|
|
|
696
767
|
void (async () => {
|
|
697
768
|
try {
|
|
698
769
|
const result = this._shouldUseRoute(args)
|
|
699
770
|
? await this._spawnRoute(args, job)
|
|
700
771
|
: await this._runAsyncWorker(args, job);
|
|
701
|
-
|
|
702
|
-
job.ok = result.ok;
|
|
703
|
-
job.status = result.ok ? 'completed' : 'failed';
|
|
704
|
-
job.providerResolved = result.providerResolved || job.providerRequested;
|
|
705
|
-
job.output = result.output || '';
|
|
706
|
-
job.stderr = result.stderr || '';
|
|
707
|
-
job.exitCode = result.exitCode ?? (result.ok ? 0 : 1);
|
|
708
|
-
job.threadId = result.threadId || null;
|
|
709
|
-
job.sessionKey = result.sessionKey || job.sessionKey || null;
|
|
710
|
-
job.completedAt = new Date().toISOString();
|
|
711
|
-
job.updatedAt = job.completedAt;
|
|
772
|
+
this._applyJobResult(job, result);
|
|
712
773
|
} catch (error) {
|
|
713
|
-
job
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
774
|
+
this._applyJobResult(job, createErrorPayload(
|
|
775
|
+
error instanceof Error ? error.message : String(error),
|
|
776
|
+
{
|
|
777
|
+
mode: 'async',
|
|
778
|
+
providerRequested: args.provider,
|
|
779
|
+
agentType: args.agentType,
|
|
780
|
+
transport: this._shouldUseRoute(args) ? 'route-script' : `${args.provider}-worker`,
|
|
781
|
+
},
|
|
782
|
+
));
|
|
720
783
|
} finally {
|
|
721
784
|
if (job.worker) {
|
|
722
785
|
await job.worker.stop().catch(() => {});
|
|
@@ -726,7 +789,7 @@ export class DelegatorMcpWorker {
|
|
|
726
789
|
}
|
|
727
790
|
})();
|
|
728
791
|
|
|
729
|
-
return
|
|
792
|
+
return this._serializeJob(job);
|
|
730
793
|
}
|
|
731
794
|
|
|
732
795
|
async _runAsyncWorker(args, job) {
|
|
@@ -745,6 +808,7 @@ export class DelegatorMcpWorker {
|
|
|
745
808
|
this._buildPromptHintInstruction(args),
|
|
746
809
|
args.developerInstructions,
|
|
747
810
|
),
|
|
811
|
+
config: getCodexMcpConfig(this._getMcpPolicyOptions(args)),
|
|
748
812
|
compactPrompt: args.compactPrompt,
|
|
749
813
|
model: args.model,
|
|
750
814
|
});
|
|
@@ -762,14 +826,13 @@ export class DelegatorMcpWorker {
|
|
|
762
826
|
if (args.provider === 'gemini') {
|
|
763
827
|
const worker = this._createGeminiWorker();
|
|
764
828
|
job.worker = worker;
|
|
765
|
-
const
|
|
829
|
+
const prompt = this._buildDirectPromptWithHint(args);
|
|
830
|
+
const result = await worker.execute(prompt, {
|
|
766
831
|
cwd: args.cwd || this.cwd,
|
|
767
832
|
timeoutMs: resolveTimeoutMs(args.agentType, args.timeoutMs),
|
|
768
833
|
model: args.model || resolveGeminiModel(args.agentType),
|
|
769
834
|
approvalMode: 'yolo',
|
|
770
|
-
allowedMcpServerNames:
|
|
771
|
-
resolveMcpProfile(args.agentType, args.mcpProfile),
|
|
772
|
-
),
|
|
835
|
+
allowedMcpServerNames: getGeminiAllowedServers(this._getMcpPolicyOptions(args)),
|
|
773
836
|
});
|
|
774
837
|
|
|
775
838
|
return {
|
|
@@ -778,6 +841,7 @@ export class DelegatorMcpWorker {
|
|
|
778
841
|
output: result.output,
|
|
779
842
|
exitCode: result.exitCode,
|
|
780
843
|
sessionKey: result.sessionKey,
|
|
844
|
+
_geminiPrompt: prompt,
|
|
781
845
|
};
|
|
782
846
|
}
|
|
783
847
|
|
|
@@ -862,7 +926,8 @@ export class DelegatorMcpWorker {
|
|
|
862
926
|
return {
|
|
863
927
|
ok: job.ok,
|
|
864
928
|
jobId: job.jobId,
|
|
865
|
-
|
|
929
|
+
job_id: job.jobId,
|
|
930
|
+
mode: job.mode || 'async',
|
|
866
931
|
status: job.status,
|
|
867
932
|
providerRequested: job.providerRequested,
|
|
868
933
|
providerResolved: job.providerResolved,
|
|
@@ -877,8 +942,88 @@ export class DelegatorMcpWorker {
|
|
|
877
942
|
stderr: job.stderr,
|
|
878
943
|
threadId: job.threadId,
|
|
879
944
|
sessionKey: job.sessionKey,
|
|
945
|
+
conversationOpen: this.geminiConversations.has(job.jobId),
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
_createJob(args, mode) {
|
|
950
|
+
const jobId = randomUUID();
|
|
951
|
+
const now = new Date().toISOString();
|
|
952
|
+
return {
|
|
953
|
+
ok: true,
|
|
954
|
+
jobId,
|
|
955
|
+
mode,
|
|
956
|
+
status: 'running',
|
|
957
|
+
providerRequested: args.provider,
|
|
958
|
+
providerResolved: null,
|
|
959
|
+
agentType: args.agentType,
|
|
960
|
+
transport: this._shouldUseRoute(args) ? 'route-script' : `${args.provider}-worker`,
|
|
961
|
+
createdAt: now,
|
|
962
|
+
startedAt: now,
|
|
963
|
+
updatedAt: now,
|
|
964
|
+
completedAt: null,
|
|
965
|
+
output: '',
|
|
966
|
+
stderr: '',
|
|
967
|
+
exitCode: null,
|
|
968
|
+
threadId: null,
|
|
969
|
+
sessionKey: args.sessionKey || null,
|
|
970
|
+
worker: null,
|
|
971
|
+
child: null,
|
|
972
|
+
requestArgs: sanitizeDelegateArgs(args),
|
|
880
973
|
};
|
|
881
974
|
}
|
|
975
|
+
|
|
976
|
+
_applyJobResult(job, result = {}) {
|
|
977
|
+
job.ok = result.ok !== false;
|
|
978
|
+
job.status = job.ok ? 'completed' : 'failed';
|
|
979
|
+
job.providerResolved = result.providerResolved || job.providerRequested;
|
|
980
|
+
job.transport = result.transport || job.transport;
|
|
981
|
+
job.output = result.output || '';
|
|
982
|
+
job.stderr = result.stderr || result.error || '';
|
|
983
|
+
job.exitCode = result.exitCode ?? (job.ok ? 0 : 1);
|
|
984
|
+
job.threadId = result.threadId || job.threadId || null;
|
|
985
|
+
job.sessionKey = result.sessionKey || job.sessionKey || null;
|
|
986
|
+
job.completedAt = new Date().toISOString();
|
|
987
|
+
job.updatedAt = job.completedAt;
|
|
988
|
+
|
|
989
|
+
if (job.providerRequested === 'gemini'
|
|
990
|
+
&& job.transport === 'gemini-worker'
|
|
991
|
+
&& typeof result._geminiPrompt === 'string') {
|
|
992
|
+
this._storeGeminiConversation(job, result._geminiPrompt, result.output || '');
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
_storeGeminiConversation(job, userPrompt, assistantReply) {
|
|
997
|
+
const existing = this.geminiConversations.get(job.jobId);
|
|
998
|
+
if (existing) {
|
|
999
|
+
if (typeof assistantReply === 'string') {
|
|
1000
|
+
const lastTurn = existing.turns.at(-1);
|
|
1001
|
+
if (lastTurn && lastTurn.assistant !== assistantReply) {
|
|
1002
|
+
lastTurn.assistant = assistantReply;
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
existing.updatedAt = new Date().toISOString();
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
this.geminiConversations.set(job.jobId, {
|
|
1010
|
+
jobId: job.jobId,
|
|
1011
|
+
closed: false,
|
|
1012
|
+
updatedAt: new Date().toISOString(),
|
|
1013
|
+
turns: [{
|
|
1014
|
+
user: userPrompt,
|
|
1015
|
+
assistant: assistantReply,
|
|
1016
|
+
}],
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
async _runSyncJob(args, extra) {
|
|
1021
|
+
const job = this._createJob(args, 'sync');
|
|
1022
|
+
this.jobs.set(job.jobId, job);
|
|
1023
|
+
const result = await this._executeDirect(args, extra);
|
|
1024
|
+
this._applyJobResult(job, result);
|
|
1025
|
+
return this._serializeJob(job);
|
|
1026
|
+
}
|
|
882
1027
|
}
|
|
883
1028
|
|
|
884
1029
|
export function createDelegatorMcpWorker(options = {}) {
|