@triflux/remote 10.50.2 → 10.52.0

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.
Files changed (134) hide show
  1. package/cto/brief.mjs +0 -20
  2. package/cto/collect.mjs +1 -200
  3. package/cto/current.schema.json +2 -23
  4. package/cto/hygiene.mjs +2 -61
  5. package/cto/lake-root.mjs +0 -56
  6. package/cto/status.mjs +5 -187
  7. package/hub/index.mjs +0 -17
  8. package/hub/team/agent-map.json +0 -1
  9. package/hub/team/agents-row-attach.py +120 -0
  10. package/hub/team/agents-row.mjs +200 -0
  11. package/hub/team/ansi.mjs +1 -35
  12. package/hub/team/backend.mjs +9 -145
  13. package/hub/team/claude-daemon-control.mjs +1 -2
  14. package/hub/team/claude-session-projection.mjs +5 -73
  15. package/hub/team/cli/commands/attach.mjs +1 -17
  16. package/hub/team/cli/commands/control.mjs +12 -38
  17. package/hub/team/cli/commands/debug.mjs +2 -39
  18. package/hub/team/cli/commands/focus.mjs +5 -31
  19. package/hub/team/cli/commands/interrupt.mjs +6 -25
  20. package/hub/team/cli/commands/kill.mjs +1 -30
  21. package/hub/team/cli/commands/list.mjs +0 -16
  22. package/hub/team/cli/commands/send.mjs +6 -26
  23. package/hub/team/cli/commands/start/index.mjs +42 -133
  24. package/hub/team/cli/commands/start/parse-args.mjs +15 -40
  25. package/hub/team/cli/commands/start/start-headless.mjs +138 -105
  26. package/hub/team/cli/commands/start/start-mux.mjs +2 -7
  27. package/hub/team/cli/commands/status.mjs +2 -57
  28. package/hub/team/cli/commands/stop.mjs +14 -19
  29. package/hub/team/cli/help.mjs +4 -5
  30. package/hub/team/cli/manifest.mjs +5 -1
  31. package/hub/team/cli/render.mjs +0 -12
  32. package/hub/team/cli/services/runtime-mode.mjs +27 -98
  33. package/hub/team/cli/services/state-store.mjs +5 -20
  34. package/hub/team/handoff.mjs +3 -34
  35. package/hub/team/headless.mjs +108 -1296
  36. package/hub/team/orchestrator.mjs +4 -30
  37. package/hub/team/pane.mjs +6 -28
  38. package/hub/team/psmux.mjs +3 -34
  39. package/hub/team/remote-session.mjs +1 -2
  40. package/hub/team/session-context.mjs +1 -1
  41. package/hub/team/session.mjs +7 -91
  42. package/hub/team/tui-core.mjs +1 -1
  43. package/hub/team/tui-viewer.mjs +11 -58
  44. package/hub/team/tui.mjs +8 -306
  45. package/hub/team/uds-orchestrator.mjs +2 -580
  46. package/hub/team/wt-manager.mjs +2 -2
  47. package/hub/workers/factory.mjs +6 -84
  48. package/hub/workers/interface.mjs +1 -7
  49. package/hub/workers/lib/jsonrpc-core.mjs +11 -0
  50. package/hub/workers/lib/jsonrpc-ws-uds.mjs +5 -8
  51. package/package.json +3 -8
  52. package/scripts/lib/agent-route-policy.mjs +143 -62
  53. package/scripts/lib/cli-claude.mjs +0 -14
  54. package/scripts/lib/env-probe.mjs +2 -131
  55. package/scripts/lib/gemini-profiles.mjs +2 -69
  56. package/scripts/lib/legacy-mcp-cleanup.mjs +283 -0
  57. package/scripts/lib/machine-profile.mjs +3 -17
  58. package/scripts/lib/mcp-filter.mjs +109 -53
  59. package/scripts/lib/mcp-guard-engine.mjs +40 -98
  60. package/scripts/lib/mcp-health.mjs +2 -2
  61. package/scripts/lib/mcp-server-catalog.mjs +2 -2
  62. package/scripts/lib/psmux-info.mjs +11 -59
  63. package/scripts/lib/skill-template.mjs +0 -245
  64. package/hub/hub-lifecycle.mjs +0 -248
  65. package/hub/pipe.mjs +0 -825
  66. package/hub/promote-penalties.mjs +0 -138
  67. package/hub/public/dashboard.html +0 -416
  68. package/hub/schema.sql +0 -209
  69. package/hub/server.mjs +0 -3048
  70. package/hub/store-adapter.mjs +0 -182
  71. package/hub/store.mjs +0 -1736
  72. package/hub/team/build-worker-prompt.mjs +0 -92
  73. package/hub/team/check-mcp-hub.mjs +0 -49
  74. package/hub/team/claude-native-bridge.mjs +0 -987
  75. package/hub/team/cli/commands/start/start-in-process.mjs +0 -50
  76. package/hub/team/cli/commands/start/start-wt.mjs +0 -86
  77. package/hub/team/cli/services/hub-client.mjs +0 -264
  78. package/hub/team/cli/services/native-control.mjs +0 -145
  79. package/hub/team/codex-review.mjs +0 -520
  80. package/hub/team/conductor-mesh-bridge.mjs +0 -122
  81. package/hub/team/conductor-registry.mjs +0 -121
  82. package/hub/team/conductor.mjs +0 -1529
  83. package/hub/team/consensus-meta.mjs +0 -176
  84. package/hub/team/cto-auto-collect.mjs +0 -115
  85. package/hub/team/cto-pull.mjs +0 -31
  86. package/hub/team/daemon-pty-tmux-bridge.mjs +0 -146
  87. package/hub/team/dashboard.mjs +0 -322
  88. package/hub/team/event-log.mjs +0 -84
  89. package/hub/team/execution-mode.mjs +0 -324
  90. package/hub/team/extract-completion-payload.mjs +0 -110
  91. package/hub/team/headless-bridge-session.mjs +0 -17
  92. package/hub/team/health-probe.mjs +0 -435
  93. package/hub/team/interactive-native-launcher.mjs +0 -119
  94. package/hub/team/interactive-native-worker.mjs +0 -80
  95. package/hub/team/interactive-tui-transport.mjs +0 -479
  96. package/hub/team/launcher-template.mjs +0 -144
  97. package/hub/team/lead-control.mjs +0 -191
  98. package/hub/team/mcp-selector.mjs +0 -145
  99. package/hub/team/native-supervisor.mjs +0 -460
  100. package/hub/team/native.mjs +0 -727
  101. package/hub/team/recovery-store.mjs +0 -149
  102. package/hub/team/remote-probe.mjs +0 -292
  103. package/hub/team/remote-watcher.mjs +0 -506
  104. package/hub/team/runtime-strategy.mjs +0 -200
  105. package/hub/team/schemas/worker-completion.json +0 -39
  106. package/hub/team/sentinel-capture.mjs +0 -127
  107. package/hub/team/session-sync.mjs +0 -316
  108. package/hub/team/swarm-cli.mjs +0 -390
  109. package/hub/team/swarm-hypervisor.mjs +0 -2206
  110. package/hub/team/swarm-intent.mjs +0 -216
  111. package/hub/team/swarm-locks.mjs +0 -335
  112. package/hub/team/swarm-planner.mjs +0 -371
  113. package/hub/team/swarm-preflight.mjs +0 -366
  114. package/hub/team/swarm-reconciler.mjs +0 -242
  115. package/hub/team/synapse-cli.mjs +0 -458
  116. package/hub/team/synapse-http.mjs +0 -311
  117. package/hub/team/synapse-registry.mjs +0 -618
  118. package/hub/team/tui-remote-adapter.mjs +0 -377
  119. package/hub/team/tui-synapse.mjs +0 -223
  120. package/hub/team/worker-completion-validator.mjs +0 -79
  121. package/hub/team/worker-sandbox.mjs +0 -125
  122. package/hub/team/worktree-lifecycle.mjs +0 -639
  123. package/hub/team/wt-templates.json +0 -43
  124. package/hub/tools.mjs +0 -900
  125. package/hub/workers/antigravity-route-worker.mjs +0 -179
  126. package/hub/workers/codex-app-server-worker.mjs +0 -1067
  127. package/hub/workers/delegator-mcp.mjs +0 -1098
  128. package/hub/workers/lib/jsonrpc-stdio.mjs +0 -204
  129. package/scripts/lib/claudemd-scanner.mjs +0 -223
  130. package/scripts/lib/context.mjs +0 -67
  131. package/scripts/lib/dynamic-route-cli.mjs +0 -107
  132. package/scripts/lib/handoff.mjs +0 -187
  133. package/scripts/lib/logger.mjs +0 -122
  134. package/scripts/lib/stealth-fetch.mjs +0 -176
@@ -1,1098 +0,0 @@
1
- #!/usr/bin/env node
2
- // hub/workers/delegator-mcp.mjs — triflux 위임용 MCP stdio 서버
3
-
4
- import { spawn } from "node:child_process";
5
- import { randomUUID } from "node:crypto";
6
- import { existsSync, readFileSync } from "node:fs";
7
- import { dirname, isAbsolute, resolve } from "node:path";
8
- import process from "node:process";
9
- import { fileURLToPath, pathToFileURL } from "node:url";
10
-
11
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
12
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
13
- import * as z from "zod";
14
- import { resolveCodexAgentPolicy } from "../../scripts/lib/agent-route-policy.mjs";
15
- import { resolveBashExecutable } from "@triflux/core/hub/lib/bash-path.mjs";
16
- import { runHeadlessWithCleanup } from "../team/headless.mjs";
17
- import { buildWorkerSandboxEnv } from "../team/worker-sandbox.mjs";
18
- import { CodexMcpWorker } from "./codex-mcp.mjs";
19
-
20
- const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
21
-
22
- // mcp-filter.mjs 동적 해석 — 프로젝트(hub/workers/)와 배포(scripts/hub/workers/) 양쪽 대응
23
- const MCP_FILTER_CANDIDATES = [
24
- resolve(SCRIPT_DIR, "../../scripts/lib/mcp-filter.mjs"), // 프로젝트 원본
25
- resolve(SCRIPT_DIR, "../../lib/mcp-filter.mjs"), // 배포 (~/.claude/scripts/)
26
- ];
27
- const mcpFilterPath = MCP_FILTER_CANDIDATES.find((p) => existsSync(p));
28
- if (!mcpFilterPath) {
29
- throw new Error(
30
- `mcp-filter.mjs not found. candidates: ${MCP_FILTER_CANDIDATES.join(", ")}`,
31
- );
32
- }
33
- const { buildPromptHint, getCodexMcpConfig, SUPPORTED_MCP_PROFILES } =
34
- await import(pathToFileURL(mcpFilterPath).href);
35
- const SERVER_INFO = { name: "triflux-delegator", version: "1.0.0" };
36
- const DEFAULT_CONTEXT_BYTES = 32 * 1024;
37
- const DIRECT_PROGRESS_START = 5;
38
- const DIRECT_PROGRESS_RUNNING = 60;
39
- const DIRECT_PROGRESS_DONE = 100;
40
- const SEARCH_ENGINE_CACHE_PATH = [".omc", "state", "search-engines.json"];
41
-
42
- const REVIEW_INSTRUCTION_BY_AGENT = Object.freeze({
43
- "code-reviewer":
44
- "코드 리뷰 모드로 동작하라. 버그, 리스크, 회귀, 테스트 누락을 우선 식별하라.",
45
- "security-reviewer":
46
- "보안 리뷰 모드로 동작하라. 취약점, 권한 경계, 비밀정보 노출 가능성을 우선 식별하라.",
47
- "quality-reviewer":
48
- "품질 리뷰 모드로 동작하라. 로직 결함, 유지보수성 저하, 테스트 누락을 우선 식별하라.",
49
- });
50
-
51
- function cloneEnv(env = process.env) {
52
- return Object.fromEntries(
53
- Object.entries(env).filter(([, value]) => typeof value === "string"),
54
- );
55
- }
56
-
57
- function parseJsonArray(raw, fallback = []) {
58
- if (!raw) return [...fallback];
59
- try {
60
- const parsed = JSON.parse(raw);
61
- return Array.isArray(parsed)
62
- ? parsed.map((item) => String(item ?? "")).filter(Boolean)
63
- : [...fallback];
64
- } catch {
65
- return [...fallback];
66
- }
67
- }
68
-
69
- function resolveCandidatePath(candidate, cwd = process.cwd()) {
70
- if (!candidate) return null;
71
- const normalized = isAbsolute(candidate)
72
- ? candidate
73
- : resolve(cwd, candidate);
74
- return existsSync(normalized) ? normalized : null;
75
- }
76
-
77
- function resolveRouteScript(explicitPath, cwd = process.cwd()) {
78
- const candidates = [
79
- explicitPath,
80
- process.env.TFX_DELEGATOR_ROUTE_SCRIPT,
81
- process.env.TFX_ROUTE_SCRIPT,
82
- resolve(SCRIPT_DIR, "..", "..", "scripts", "tfx-route.sh"),
83
- resolve(cwd, "scripts", "tfx-route.sh"),
84
- ];
85
-
86
- for (const candidate of candidates) {
87
- const resolved = resolveCandidatePath(candidate, cwd);
88
- if (resolved) return resolved;
89
- }
90
-
91
- return null;
92
- }
93
-
94
- function resolveCodexProfile(agentType) {
95
- return resolveCodexAgentPolicy(agentType).profile;
96
- }
97
-
98
- function normalizeProvider(provider = "auto") {
99
- const value = provider || "auto";
100
- return value === "gemini" ? "antigravity" : value;
101
- }
102
-
103
- function normalizeDelegateArgs(args = {}) {
104
- return { ...args, provider: normalizeProvider(args.provider) };
105
- }
106
-
107
- function resolveTimeoutMs(agentType, timeoutMs) {
108
- if (Number.isFinite(timeoutMs) && timeoutMs > 0) {
109
- return Math.trunc(timeoutMs);
110
- }
111
-
112
- return resolveCodexAgentPolicy(agentType).timeoutSec * 1000;
113
- }
114
-
115
- function resolveTimeoutSec(agentType, timeoutMs) {
116
- const resolved = resolveTimeoutMs(agentType, timeoutMs);
117
- return Math.max(1, Math.ceil(resolved / 1000));
118
- }
119
-
120
- function loadContextFromFile(contextFile) {
121
- if (!contextFile) return "";
122
- const resolved = resolveCandidatePath(contextFile);
123
- if (!resolved) return "";
124
- try {
125
- return readFileSync(resolved, "utf8").slice(0, DEFAULT_CONTEXT_BYTES);
126
- } catch {
127
- return "";
128
- }
129
- }
130
-
131
- function withContext(prompt, contextFile) {
132
- const context = loadContextFromFile(contextFile);
133
- if (!context) return prompt;
134
- return `${prompt}\n\n<prior_context>\n${context}\n</prior_context>`;
135
- }
136
-
137
- function joinInstructions(...values) {
138
- return values
139
- .filter((value) => typeof value === "string" && value.trim())
140
- .join("\n")
141
- .trim();
142
- }
143
-
144
- function _loadAvailableServersFromSearchEngineCache(cwd = process.cwd()) {
145
- const cacheFile = resolve(cwd, ...SEARCH_ENGINE_CACHE_PATH);
146
- if (!existsSync(cacheFile)) return null;
147
-
148
- try {
149
- const parsed = JSON.parse(readFileSync(cacheFile, "utf8"));
150
- if (!Array.isArray(parsed?.engines)) return null;
151
- return parsed.engines
152
- .filter((engine) => engine?.status === "available")
153
- .map((engine) =>
154
- typeof engine?.name === "string" ? engine.name.trim() : "",
155
- )
156
- .filter(Boolean);
157
- } catch {
158
- return null;
159
- }
160
- }
161
-
162
- function parseRouteType(stderr = "") {
163
- const match = stderr.match(/type=([a-z-]+)/);
164
- if (!match) return null;
165
- if (match[1] === "codex") return "codex";
166
- if (match[1] === "gemini") return "antigravity";
167
- return match[1];
168
- }
169
-
170
- function summarizePayload(payload) {
171
- if (typeof payload.output === "string" && payload.output.trim())
172
- return payload.output.trim();
173
- if (payload.mode === "async" && payload.job_id)
174
- return `비동기 위임이 시작되었습니다. jobId=${payload.job_id}`;
175
- if (payload.job_id) return `jobId=${payload.job_id} 상태=${payload.status}`;
176
- if (payload.status) return `상태=${payload.status}`;
177
- return payload.ok ? "완료되었습니다." : "실패했습니다.";
178
- }
179
-
180
- function createToolResponse(payload, { isError = false } = {}) {
181
- return {
182
- content: [{ type: "text", text: summarizePayload(payload) }],
183
- structuredContent: payload,
184
- isError,
185
- };
186
- }
187
-
188
- function createErrorPayload(message, extras = {}) {
189
- return {
190
- ok: false,
191
- status: "failed",
192
- error: message,
193
- ...extras,
194
- };
195
- }
196
-
197
- const DelegateInputSchema = z.object({
198
- prompt: z.string().min(1).describe("위임할 프롬프트"),
199
- provider: z
200
- .enum(["auto", "codex", "antigravity", "gemini"])
201
- .default("auto")
202
- .describe("사용할 provider"),
203
- mode: z
204
- .enum(["sync", "async"])
205
- .default("sync")
206
- .describe("동기 또는 비동기 실행"),
207
- agentType: z
208
- .string()
209
- .default("executor")
210
- .describe("tfx-route 역할명 또는 direct 실행 역할"),
211
- cwd: z.string().optional().describe("작업 디렉터리"),
212
- timeoutMs: z
213
- .number()
214
- .int()
215
- .positive()
216
- .optional()
217
- .describe("요청 타임아웃(ms)"),
218
- sessionKey: z.string().optional().describe("Codex warm session 재사용 키"),
219
- resetSession: z.boolean().optional().describe("기존 Codex 세션 초기화 여부"),
220
- mcpProfile: z.enum(SUPPORTED_MCP_PROFILES).default("auto"),
221
- contextFile: z
222
- .string()
223
- .optional()
224
- .describe("tfx-route prior_context 파일 경로"),
225
- availableServers: z
226
- .array(z.string())
227
- .optional()
228
- .describe("Codex에 등록된 MCP 서버 이름 목록 (config override 대상)"),
229
- searchTool: z
230
- .enum(["brave-search", "tavily", "exa"])
231
- .optional()
232
- .describe("검색 우선 도구"),
233
- workerIndex: z
234
- .number()
235
- .int()
236
- .positive()
237
- .optional()
238
- .describe("병렬 워커 인덱스"),
239
- model: z.string().optional().describe("직접 실행 시 모델 오버라이드"),
240
- developerInstructions: z
241
- .string()
242
- .optional()
243
- .describe("직접 실행 시 추가 개발자 지침"),
244
- compactPrompt: z.string().optional().describe("Codex compact prompt"),
245
- threadId: z.string().optional().describe("Codex 직접 실행 시 기존 threadId"),
246
- codexTransport: z
247
- .enum(["auto", "mcp", "exec"])
248
- .optional()
249
- .describe("route 경로용 Codex transport"),
250
- noClaudeNative: z
251
- .boolean()
252
- .optional()
253
- .describe("route 경로용 TFX_NO_CLAUDE_NATIVE"),
254
- teamName: z.string().optional().describe("TFX_TEAM_NAME"),
255
- teamTaskId: z.string().optional().describe("TFX_TEAM_TASK_ID"),
256
- teamAgentName: z.string().optional().describe("TFX_TEAM_AGENT_NAME"),
257
- teamLeadName: z.string().optional().describe("TFX_TEAM_LEAD_NAME"),
258
- hubUrl: z.string().optional().describe("TFX_HUB_URL"),
259
- workers: z
260
- .array(
261
- z.object({
262
- provider: z
263
- .enum(["codex", "antigravity", "gemini"])
264
- .describe("워커 provider"),
265
- agentType: z.string().default("executor").describe("역할명"),
266
- prompt: z.string().describe("워커별 프롬프트"),
267
- mcpProfile: z.string().default("auto").describe("MCP 프로필"),
268
- model: z.string().optional().describe("모델 오버라이드"),
269
- }),
270
- )
271
- .optional()
272
- .describe("병렬 멀티워커 목록. 지정 시 psmux 기반 병렬 실행"),
273
- });
274
-
275
- const DelegateStatusInputSchema = z.object({
276
- jobId: z.string().min(1).describe("조회할 비동기 job ID"),
277
- });
278
-
279
- const DelegateReplyInputSchema = z.object({
280
- job_id: z.string().min(1).describe("후속 응답을 보낼 기존 delegate job ID"),
281
- reply: z.string().min(1).describe("후속 사용자 응답"),
282
- done: z
283
- .boolean()
284
- .default(false)
285
- .describe("true이면 응답 처리 후 대화를 종료"),
286
- });
287
-
288
- const DelegateOutputSchema = z.object({
289
- ok: z.boolean(),
290
- jobId: z.string().optional(),
291
- job_id: z.string().optional(),
292
- mode: z.enum(["sync", "async"]).optional(),
293
- status: z.enum(["running", "completed", "failed", "partial"]).optional(),
294
- error: z.string().optional(),
295
- providerRequested: z.string().optional(),
296
- providerResolved: z.string().nullable().optional(),
297
- agentType: z.string().optional(),
298
- transport: z.string().optional(),
299
- createdAt: z.string().optional(),
300
- startedAt: z.string().optional(),
301
- updatedAt: z.string().optional(),
302
- completedAt: z.string().nullable().optional(),
303
- exitCode: z.number().nullable().optional(),
304
- output: z.string().optional(),
305
- stderr: z.string().optional(),
306
- threadId: z.string().nullable().optional(),
307
- sessionKey: z.string().nullable().optional(),
308
- conversationOpen: z.boolean().optional(),
309
- workerResults: z
310
- .array(
311
- z.object({
312
- cli: z.string(),
313
- role: z.string().optional(),
314
- paneName: z.string(),
315
- matched: z.boolean(),
316
- exitCode: z.number().nullable(),
317
- output: z.string(),
318
- }),
319
- )
320
- .optional()
321
- .describe("멀티워커 개별 결과"),
322
- sessionName: z.string().optional().describe("psmux 세션명"),
323
- });
324
-
325
- function isTeamRouteRequested(args) {
326
- return Boolean(
327
- args.teamName ||
328
- args.teamTaskId ||
329
- args.teamAgentName ||
330
- args.teamLeadName ||
331
- args.hubUrl,
332
- );
333
- }
334
-
335
- function pickRouteMode(provider) {
336
- const normalized = normalizeProvider(provider);
337
- return normalized === "auto" ? "auto" : normalized;
338
- }
339
-
340
- function sanitizeDelegateArgs(args = {}) {
341
- return {
342
- provider: normalizeProvider(args.provider),
343
- agentType: args.agentType || "executor",
344
- cwd: args.cwd || null,
345
- timeoutMs: Number.isFinite(Number(args.timeoutMs))
346
- ? Math.trunc(Number(args.timeoutMs))
347
- : null,
348
- sessionKey: args.sessionKey || null,
349
- resetSession: Boolean(args.resetSession),
350
- mcpProfile: args.mcpProfile || "auto",
351
- contextFile: args.contextFile || null,
352
- availableServers: Array.isArray(args.availableServers)
353
- ? args.availableServers
354
- : null,
355
- searchTool: args.searchTool || null,
356
- workerIndex: Number.isInteger(args.workerIndex) ? args.workerIndex : null,
357
- model: args.model || null,
358
- developerInstructions: args.developerInstructions || null,
359
- compactPrompt: args.compactPrompt || null,
360
- threadId: args.threadId || null,
361
- codexTransport: args.codexTransport || null,
362
- noClaudeNative: args.noClaudeNative === true,
363
- teamName: args.teamName || null,
364
- teamTaskId: args.teamTaskId || null,
365
- teamAgentName: args.teamAgentName || null,
366
- teamLeadName: args.teamLeadName || null,
367
- hubUrl: args.hubUrl || null,
368
- };
369
- }
370
-
371
- async function emitProgress(extra, progress, total, message) {
372
- if (extra?._meta?.progressToken === undefined) return;
373
- await extra.sendNotification({
374
- method: "notifications/progress",
375
- params: {
376
- progressToken: extra._meta.progressToken,
377
- progress,
378
- total,
379
- message,
380
- },
381
- });
382
- }
383
-
384
- export class DelegatorMcpWorker {
385
- type = "delegator";
386
-
387
- constructor(options = {}) {
388
- this.cwd = options.cwd || process.cwd();
389
- this.env = cloneEnv({ ...cloneEnv(process.env), ...cloneEnv(options.env) });
390
- this.routeScript = resolveRouteScript(options.routeScript, this.cwd);
391
- this.bashCommand =
392
- options.bashCommand ||
393
- this.env.TFX_DELEGATOR_BASH_COMMAND ||
394
- this.env.BASH_BIN ||
395
- resolveBashExecutable();
396
-
397
- this.codexWorker = new CodexMcpWorker({
398
- command:
399
- options.codexCommand ||
400
- this.env.TFX_DELEGATOR_CODEX_COMMAND ||
401
- this.env.CODEX_BIN ||
402
- "codex",
403
- args:
404
- Array.isArray(options.codexArgs) && options.codexArgs.length
405
- ? options.codexArgs
406
- : parseJsonArray(this.env.TFX_DELEGATOR_CODEX_ARGS_JSON, []),
407
- cwd: this.cwd,
408
- env: this.env,
409
- clientInfo: { name: SERVER_INFO.name, version: SERVER_INFO.version },
410
- });
411
-
412
- this.server = null;
413
- this.transport = null;
414
- this.jobs = new Map();
415
- this.routeChildren = new Set();
416
- this.ready = false;
417
- }
418
-
419
- isReady() {
420
- return this.ready;
421
- }
422
-
423
- async start() {
424
- if (this.server) {
425
- this.ready = true;
426
- return;
427
- }
428
-
429
- const server = new McpServer(SERVER_INFO, {
430
- capabilities: { logging: {} },
431
- });
432
-
433
- server.registerTool(
434
- "triflux-delegate",
435
- {
436
- description:
437
- "새 위임을 실행합니다. codex direct 경로와 antigravity/tfx-route 기반 auto 라우팅을 지원합니다.",
438
- inputSchema: DelegateInputSchema,
439
- outputSchema: DelegateOutputSchema,
440
- },
441
- async (args, extra) => {
442
- const payload = await this.delegate(args, extra);
443
- return createToolResponse(payload, {
444
- isError: payload.ok === false && payload.mode !== "async",
445
- });
446
- },
447
- );
448
-
449
- server.registerTool(
450
- "triflux-delegate-status",
451
- {
452
- description: "비동기 위임 job 상태를 조회합니다.",
453
- inputSchema: DelegateStatusInputSchema,
454
- outputSchema: DelegateOutputSchema,
455
- },
456
- async ({ jobId }, extra) => {
457
- const payload = await this.getJobStatus(jobId, extra);
458
- return createToolResponse(payload, { isError: payload.ok === false });
459
- },
460
- );
461
-
462
- server.registerTool(
463
- "triflux-delegate-reply",
464
- {
465
- description:
466
- "기존 delegate job에 후속 응답을 보냅니다. 현재 route 기반 Antigravity job은 후속 응답을 지원하지 않습니다.",
467
- inputSchema: DelegateReplyInputSchema,
468
- outputSchema: DelegateOutputSchema,
469
- },
470
- async (args, extra) => {
471
- const payload = await this.reply(args, extra);
472
- return createToolResponse(payload, { isError: payload.ok === false });
473
- },
474
- );
475
-
476
- this.server = server;
477
- this.ready = true;
478
- }
479
-
480
- async serveStdio() {
481
- await this.start();
482
- if (this.transport) return;
483
- const transport = new StdioServerTransport();
484
- await this.server.connect(transport);
485
- this.transport = transport;
486
- }
487
-
488
- async stop() {
489
- this.ready = false;
490
-
491
- for (const child of this.routeChildren) {
492
- try {
493
- child.kill();
494
- } catch {}
495
- }
496
- this.routeChildren.clear();
497
-
498
- await this.codexWorker.stop().catch(() => {});
499
- if (this.server) {
500
- await this.server.close().catch(() => {});
501
- }
502
-
503
- this.server = null;
504
- this.transport = null;
505
- }
506
-
507
- async run(prompt, options = {}) {
508
- return this._executeDirect(normalizeDelegateArgs({ prompt, ...options }));
509
- }
510
-
511
- async execute(prompt, options = {}) {
512
- const result = await this._executeDirect(
513
- normalizeDelegateArgs({ prompt, ...options }),
514
- );
515
- return {
516
- output: result.output || result.error || "",
517
- exitCode: result.exitCode ?? (result.ok ? 0 : 1),
518
- threadId: result.threadId || null,
519
- sessionKey: result.sessionKey || null,
520
- raw: result,
521
- };
522
- }
523
-
524
- async delegate(args, extra) {
525
- const normalizedArgs = normalizeDelegateArgs(args);
526
- if (normalizedArgs.mode === "async") {
527
- return this._startAsyncJob(normalizedArgs, extra);
528
- }
529
- return this._runSyncJob(normalizedArgs, extra);
530
- }
531
-
532
- async getJobStatus(jobId, extra) {
533
- const job = this.jobs.get(jobId);
534
- if (!job) {
535
- return createErrorPayload(`알 수 없는 jobId: ${jobId}`, { jobId });
536
- }
537
-
538
- const payload = this._serializeJob(job);
539
- if (job.status === "running") {
540
- await emitProgress(extra, 25, 100, `job ${jobId} 실행 중`);
541
- } else if (job.status === "completed") {
542
- await emitProgress(extra, DIRECT_PROGRESS_DONE, 100, `job ${jobId} 완료`);
543
- } else if (job.status === "failed") {
544
- await emitProgress(extra, DIRECT_PROGRESS_DONE, 100, `job ${jobId} 실패`);
545
- }
546
- return payload;
547
- }
548
-
549
- async reply({ job_id }, extra) {
550
- const job = this.jobs.get(job_id);
551
- if (!job) {
552
- return createErrorPayload(`알 수 없는 jobId: ${job_id}`, {
553
- jobId: job_id,
554
- job_id,
555
- });
556
- }
557
- if (job.status === "running") {
558
- return createErrorPayload(`job ${job_id}가 아직 실행 중입니다.`, {
559
- jobId: job_id,
560
- job_id,
561
- });
562
- }
563
- void extra;
564
- return createErrorPayload(
565
- "delegate-reply는 route 기반 Antigravity job에 지원되지 않습니다.",
566
- {
567
- jobId: job_id,
568
- job_id,
569
- },
570
- );
571
- }
572
-
573
- _buildDirectPrompt(args) {
574
- return withContext(String(args.prompt ?? ""), args.contextFile);
575
- }
576
-
577
- _getMcpPolicyOptions(args) {
578
- return {
579
- agentType: args.agentType || "executor",
580
- requestedProfile: args.mcpProfile || "auto",
581
- searchTool: args.searchTool,
582
- workerIndex: Number.isInteger(args.workerIndex)
583
- ? args.workerIndex
584
- : undefined,
585
- taskText: withContext(String(args.prompt ?? ""), args.contextFile),
586
- availableServers: Array.isArray(args.availableServers)
587
- ? args.availableServers
588
- : undefined,
589
- };
590
- }
591
-
592
- _buildPromptHintInstruction(args) {
593
- return buildPromptHint(this._getMcpPolicyOptions(args));
594
- }
595
-
596
- _shouldUseRoute(args) {
597
- return (
598
- args.provider === "auto" ||
599
- args.provider === "antigravity" ||
600
- args.provider === "gemini" ||
601
- isTeamRouteRequested(args)
602
- );
603
- }
604
-
605
- async _executeDirect(args, extra = null) {
606
- await emitProgress(
607
- extra,
608
- DIRECT_PROGRESS_START,
609
- 100,
610
- "위임 실행을 시작합니다.",
611
- );
612
-
613
- // 멀티워커 분기: workers 배열이 있으면 headless psmux 병렬 실행
614
- if (Array.isArray(args.workers) && args.workers.length > 0) {
615
- try {
616
- const result = await this._executeMultiWorker(args, extra);
617
- await emitProgress(
618
- extra,
619
- DIRECT_PROGRESS_DONE,
620
- 100,
621
- "위임이 완료되었습니다.",
622
- );
623
- return result;
624
- } catch (error) {
625
- const message = error instanceof Error ? error.message : String(error);
626
- return createErrorPayload(message, {
627
- mode: "sync",
628
- providerRequested: "multi",
629
- agentType: args.agentType,
630
- transport: "headless-psmux",
631
- });
632
- }
633
- }
634
-
635
- const runViaRoute = this._shouldUseRoute(args);
636
-
637
- try {
638
- const result = runViaRoute
639
- ? await this._executeRoute(args, extra)
640
- : await this._executeWorker(args, extra);
641
-
642
- await emitProgress(
643
- extra,
644
- DIRECT_PROGRESS_DONE,
645
- 100,
646
- "위임이 완료되었습니다.",
647
- );
648
- return result;
649
- } catch (error) {
650
- const message = error instanceof Error ? error.message : String(error);
651
- return createErrorPayload(message, {
652
- mode: "sync",
653
- providerRequested: args.provider,
654
- agentType: args.agentType,
655
- transport: runViaRoute ? "route-script" : `${args.provider}-worker`,
656
- });
657
- }
658
- }
659
-
660
- async _executeMultiWorker(args, extra) {
661
- await emitProgress(
662
- extra,
663
- DIRECT_PROGRESS_START,
664
- 100,
665
- "멀티워커 psmux 병렬 실행을 시작합니다.",
666
- );
667
-
668
- // workers 배열을 headless assignments 형식으로 변환
669
- const assignments = args.workers.map((w) => {
670
- // provider를 CLI 이름으로 매핑. gemini는 deprecated alias로 antigravity에 합류한다.
671
- const cli = normalizeProvider(w.provider);
672
- const role = w.agentType || "executor";
673
- // buildDirectPrompt와 동일한 context 처리
674
- const prompt = withContext(String(w.prompt || ""), args.contextFile);
675
- return { cli, prompt, role };
676
- });
677
-
678
- // 타임아웃: 워커 중 가장 긴 타임아웃 사용
679
- const maxTimeoutSec = Math.max(
680
- ...args.workers.map((w) =>
681
- Math.ceil(
682
- resolveTimeoutMs(w.agentType || "executor", args.timeoutMs) / 1000,
683
- ),
684
- ),
685
- 300,
686
- );
687
-
688
- try {
689
- const { results, sessionName } = await runHeadlessWithCleanup(
690
- assignments,
691
- {
692
- sessionPrefix: "dlg",
693
- timeoutSec: maxTimeoutSec,
694
- layout: assignments.length <= 2 ? "even-horizontal" : "2x2",
695
- progressive: true,
696
- dashboard: true,
697
- dashboardLayout: "single",
698
- },
699
- );
700
-
701
- await emitProgress(
702
- extra,
703
- DIRECT_PROGRESS_DONE,
704
- 100,
705
- `멀티워커 실행 완료: ${results.length}개 워커`,
706
- );
707
-
708
- // 전체 성공 판단: 모든 워커가 matched && exitCode === 0
709
- const allOk = results.every((r) => r.matched && r.exitCode === 0);
710
- // 개별 출력을 합산
711
- const combinedOutput = results
712
- .map(
713
- (r, i) =>
714
- `=== Worker ${i + 1} (${r.cli}/${assignments[i].role}) ===\n${r.output || "(no output)"}`,
715
- )
716
- .join("\n\n");
717
-
718
- return {
719
- ok: allOk,
720
- mode: "sync",
721
- status: allOk ? "completed" : "partial",
722
- providerRequested: "multi",
723
- providerResolved: "multi",
724
- agentType: args.agentType || "executor",
725
- transport: "headless-psmux",
726
- exitCode: allOk ? 0 : 1,
727
- output: combinedOutput,
728
- workerResults: results.map((r) => ({
729
- cli: r.cli,
730
- role: r.role || "",
731
- paneName: r.paneName,
732
- matched: r.matched,
733
- exitCode: r.exitCode,
734
- output: r.output || "",
735
- })),
736
- sessionName,
737
- };
738
- } catch (error) {
739
- const message = error instanceof Error ? error.message : String(error);
740
- return createErrorPayload(message, {
741
- mode: "sync",
742
- providerRequested: "multi",
743
- agentType: args.agentType || "executor",
744
- transport: "headless-psmux",
745
- });
746
- }
747
- }
748
-
749
- async _executeWorker(args, extra) {
750
- await emitProgress(
751
- extra,
752
- DIRECT_PROGRESS_RUNNING,
753
- 100,
754
- "직접 워커 경로로 실행 중입니다.",
755
- );
756
-
757
- if (args.provider === "codex") {
758
- const result = await this.codexWorker.execute(
759
- this._buildDirectPrompt(args),
760
- {
761
- cwd: args.cwd || this.cwd,
762
- timeoutMs: resolveTimeoutMs(args.agentType, args.timeoutMs),
763
- sessionKey: args.sessionKey,
764
- threadId: args.threadId,
765
- resetSession: args.resetSession,
766
- profile: resolveCodexProfile(args.agentType),
767
- sandbox: "danger-full-access",
768
- approvalPolicy: "never",
769
- developerInstructions: joinInstructions(
770
- REVIEW_INSTRUCTION_BY_AGENT[args.agentType],
771
- this._buildPromptHintInstruction(args),
772
- args.developerInstructions,
773
- ),
774
- config: getCodexMcpConfig(this._getMcpPolicyOptions(args)),
775
- compactPrompt: args.compactPrompt,
776
- model: args.model,
777
- },
778
- );
779
-
780
- return {
781
- ok: result.exitCode === 0,
782
- mode: "sync",
783
- status: result.exitCode === 0 ? "completed" : "failed",
784
- providerRequested: "codex",
785
- providerResolved: "codex",
786
- agentType: args.agentType,
787
- transport: "codex-mcp",
788
- exitCode: result.exitCode,
789
- output: result.output,
790
- sessionKey: result.sessionKey,
791
- threadId: result.threadId,
792
- };
793
- }
794
-
795
- return createErrorPayload(
796
- `지원하지 않는 direct provider: ${args.provider}`,
797
- {
798
- mode: "sync",
799
- providerRequested: args.provider,
800
- agentType: args.agentType,
801
- transport: "direct-worker",
802
- },
803
- );
804
- }
805
-
806
- async _executeRoute(args, extra) {
807
- if (!this.routeScript) {
808
- return createErrorPayload("tfx-route.sh 경로를 찾지 못했습니다.", {
809
- mode: "sync",
810
- providerRequested: args.provider,
811
- agentType: args.agentType,
812
- transport: "route-script",
813
- });
814
- }
815
-
816
- await emitProgress(
817
- extra,
818
- DIRECT_PROGRESS_RUNNING,
819
- 100,
820
- "tfx-route.sh 경로로 실행 중입니다.",
821
- );
822
- const result = await this._spawnRoute(args);
823
- return {
824
- ok: result.exitCode === 0,
825
- mode: "sync",
826
- status: result.exitCode === 0 ? "completed" : "failed",
827
- providerRequested: args.provider,
828
- providerResolved: parseRouteType(result.stderr) || args.provider,
829
- agentType: args.agentType,
830
- transport: "route-script",
831
- exitCode: result.exitCode,
832
- output: result.stdout.trim() || result.stderr.trim(),
833
- stderr: result.stderr.trim(),
834
- };
835
- }
836
-
837
- async _startAsyncJob(args, extra) {
838
- const job = this._createJob(args, "async");
839
- this.jobs.set(job.jobId, job);
840
-
841
- await emitProgress(
842
- extra,
843
- DIRECT_PROGRESS_START,
844
- 100,
845
- `비동기 job ${job.jobId}를 시작합니다.`,
846
- );
847
-
848
- void (async () => {
849
- try {
850
- const result = this._shouldUseRoute(args)
851
- ? await this._spawnRoute(args, job)
852
- : await this._runAsyncWorker(args, job);
853
- this._applyJobResult(job, result);
854
- } catch (error) {
855
- this._applyJobResult(
856
- job,
857
- createErrorPayload(
858
- error instanceof Error ? error.message : String(error),
859
- {
860
- mode: "async",
861
- providerRequested: args.provider,
862
- agentType: args.agentType,
863
- transport: this._shouldUseRoute(args)
864
- ? "route-script"
865
- : `${args.provider}-worker`,
866
- },
867
- ),
868
- );
869
- } finally {
870
- if (job.worker) {
871
- await job.worker.stop().catch(() => {});
872
- job.worker = null;
873
- }
874
- job.child = null;
875
- }
876
- })();
877
-
878
- return this._serializeJob(job);
879
- }
880
-
881
- async _runAsyncWorker(args, job) {
882
- if (args.provider === "codex") {
883
- const result = await this.codexWorker.execute(
884
- this._buildDirectPrompt(args),
885
- {
886
- cwd: args.cwd || this.cwd,
887
- timeoutMs: resolveTimeoutMs(args.agentType, args.timeoutMs),
888
- sessionKey: args.sessionKey,
889
- threadId: args.threadId,
890
- resetSession: args.resetSession,
891
- profile: resolveCodexProfile(args.agentType),
892
- sandbox: "danger-full-access",
893
- approvalPolicy: "never",
894
- developerInstructions: joinInstructions(
895
- REVIEW_INSTRUCTION_BY_AGENT[args.agentType],
896
- this._buildPromptHintInstruction(args),
897
- args.developerInstructions,
898
- ),
899
- config: getCodexMcpConfig(this._getMcpPolicyOptions(args)),
900
- compactPrompt: args.compactPrompt,
901
- model: args.model,
902
- },
903
- );
904
-
905
- return {
906
- ok: result.exitCode === 0,
907
- providerResolved: "codex",
908
- output: result.output,
909
- exitCode: result.exitCode,
910
- threadId: result.threadId,
911
- sessionKey: result.sessionKey,
912
- };
913
- }
914
-
915
- throw new Error(`지원하지 않는 async provider: ${args.provider}`);
916
- }
917
-
918
- _buildRouteEnv(args) {
919
- const baseEnv = cloneEnv(this.env);
920
- const sandbox = buildWorkerSandboxEnv({
921
- cwd: args.cwd || this.cwd,
922
- sessionId:
923
- args.teamTaskId ||
924
- args.teamAgentName ||
925
- args.workerIndex ||
926
- args.agentType ||
927
- "route",
928
- agent: args.provider,
929
- env: baseEnv,
930
- });
931
- const env = { ...baseEnv, ...sandbox.env };
932
- env.TFX_CLI_MODE = pickRouteMode(args.provider);
933
- if (sandbox.root) env.TFX_WORKER_SANDBOX_SCOPE = "delegator-route";
934
-
935
- if (args.codexTransport) {
936
- env.TFX_CODEX_TRANSPORT = args.codexTransport;
937
- }
938
- if (args.noClaudeNative === true) {
939
- env.TFX_NO_CLAUDE_NATIVE = "1";
940
- }
941
- if (args.searchTool) {
942
- env.TFX_SEARCH_TOOL = args.searchTool;
943
- }
944
- if (Number.isInteger(args.workerIndex) && args.workerIndex > 0) {
945
- env.TFX_WORKER_INDEX = String(args.workerIndex);
946
- }
947
- if (args.teamName) env.TFX_TEAM_NAME = args.teamName;
948
- if (args.teamTaskId) env.TFX_TEAM_TASK_ID = args.teamTaskId;
949
- if (args.teamAgentName) env.TFX_TEAM_AGENT_NAME = args.teamAgentName;
950
- if (args.teamLeadName) env.TFX_TEAM_LEAD_NAME = args.teamLeadName;
951
- if (args.hubUrl) env.TFX_HUB_URL = args.hubUrl;
952
-
953
- return env;
954
- }
955
-
956
- async _spawnRoute(args, job = null) {
957
- const prompt = withContext(String(args.prompt ?? ""), args.contextFile);
958
- const childArgs = [
959
- this.routeScript,
960
- args.agentType || "executor",
961
- prompt,
962
- args.mcpProfile || "auto",
963
- String(resolveTimeoutSec(args.agentType, args.timeoutMs)),
964
- ];
965
-
966
- const child = spawn(this.bashCommand, childArgs, {
967
- cwd: args.cwd || this.cwd,
968
- env: this._buildRouteEnv(args),
969
- stdio: ["ignore", "pipe", "pipe"],
970
- windowsHide: true,
971
- });
972
-
973
- if (job) {
974
- job.child = child;
975
- }
976
-
977
- this.routeChildren.add(child);
978
-
979
- return await new Promise((resolvePromise, rejectPromise) => {
980
- const stdoutChunks = [];
981
- const stderrChunks = [];
982
-
983
- child.stdout.on("data", (chunk) => stdoutChunks.push(Buffer.from(chunk)));
984
- child.stderr.on("data", (chunk) => stderrChunks.push(Buffer.from(chunk)));
985
- child.once("error", (error) => {
986
- this.routeChildren.delete(child);
987
- rejectPromise(error);
988
- });
989
- child.once("close", (code) => {
990
- this.routeChildren.delete(child);
991
- const stdout = Buffer.concat(stdoutChunks).toString("utf8");
992
- const stderr = Buffer.concat(stderrChunks).toString("utf8");
993
- resolvePromise({
994
- ok: code === 0,
995
- providerResolved: parseRouteType(stderr) || args.provider,
996
- output: stdout.trim() || stderr.trim(),
997
- stdout,
998
- stderr,
999
- exitCode: code ?? 1,
1000
- });
1001
- });
1002
- });
1003
- }
1004
-
1005
- _serializeJob(job) {
1006
- return {
1007
- ok: job.ok,
1008
- job_id: job.jobId,
1009
- mode: job.mode || "async",
1010
- status: job.status,
1011
- provider_requested: job.providerRequested,
1012
- provider_resolved: job.providerResolved,
1013
- agent_type: job.agentType,
1014
- transport: job.transport,
1015
- created_at: job.createdAt,
1016
- started_at: job.startedAt,
1017
- updated_at: job.updatedAt,
1018
- completed_at: job.completedAt,
1019
- output: job.output,
1020
- stderr: job.stderr,
1021
- error: "",
1022
- thread_id: job.threadId,
1023
- session_key: job.sessionKey,
1024
- conversation_open: false,
1025
- };
1026
- }
1027
-
1028
- _createJob(args, mode) {
1029
- const jobId = randomUUID();
1030
- const now = new Date().toISOString();
1031
- return {
1032
- ok: true,
1033
- jobId,
1034
- mode,
1035
- status: "running",
1036
- providerRequested: args.provider,
1037
- providerResolved: null,
1038
- agentType: args.agentType,
1039
- transport: this._shouldUseRoute(args)
1040
- ? "route-script"
1041
- : `${args.provider}-worker`,
1042
- createdAt: now,
1043
- startedAt: now,
1044
- updatedAt: now,
1045
- completedAt: null,
1046
- output: "",
1047
- stderr: "",
1048
- exitCode: null,
1049
- threadId: null,
1050
- sessionKey: args.sessionKey || null,
1051
- worker: null,
1052
- child: null,
1053
- requestArgs: sanitizeDelegateArgs(args),
1054
- };
1055
- }
1056
-
1057
- _applyJobResult(job, result = {}) {
1058
- job.ok = result.ok !== false;
1059
- job.status = job.ok ? "completed" : "failed";
1060
- job.providerResolved = result.providerResolved || job.providerRequested;
1061
- job.transport = result.transport || job.transport;
1062
- job.output = result.output || "";
1063
- job.stderr = result.stderr || result.error || "";
1064
- job.exitCode = result.exitCode ?? (job.ok ? 0 : 1);
1065
- job.threadId = result.threadId || job.threadId || null;
1066
- job.sessionKey = result.sessionKey || job.sessionKey || null;
1067
- job.completedAt = new Date().toISOString();
1068
- job.updatedAt = job.completedAt;
1069
- }
1070
-
1071
- async _runSyncJob(args, extra) {
1072
- const job = this._createJob(args, "sync");
1073
- this.jobs.set(job.jobId, job);
1074
- const result = await this._executeDirect(args, extra);
1075
- this._applyJobResult(job, result);
1076
- return this._serializeJob(job);
1077
- }
1078
- }
1079
-
1080
- export function createDelegatorMcpWorker(options = {}) {
1081
- return new DelegatorMcpWorker(options);
1082
- }
1083
-
1084
- export async function runDelegatorMcpCli() {
1085
- const worker = createDelegatorMcpWorker();
1086
- try {
1087
- await worker.serveStdio();
1088
- } catch (error) {
1089
- console.error(
1090
- `[delegator-mcp] ${error instanceof Error ? error.message : String(error)}`,
1091
- );
1092
- process.exitCode = 1;
1093
- }
1094
- }
1095
-
1096
- if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
1097
- await runDelegatorMcpCli();
1098
- }