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