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