@triflux/remote 10.1.0 → 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 +61 -0
- package/hub/server.mjs +2165 -708
- 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 +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 +125 -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 +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 +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 +20 -118
- 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 +1020 -279
- 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 +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 +1522 -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 +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 +18 -34
- package/hub/team/tui-synapse.mjs +223 -0
- package/hub/team/tui-viewer.mjs +104 -44
- package/hub/team/tui-widgets.mjs +262 -0
- package/hub/team/tui.mjs +927 -437
- 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 +517 -63
- 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 +662 -335
- 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,30 +1,35 @@
|
|
|
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,
|
|
@@ -32,74 +37,77 @@ const {
|
|
|
32
37
|
getGeminiAllowedServers,
|
|
33
38
|
SUPPORTED_MCP_PROFILES,
|
|
34
39
|
} = await import(pathToFileURL(mcpFilterPath).href);
|
|
35
|
-
const SERVER_INFO = { name:
|
|
40
|
+
const SERVER_INFO = { name: "triflux-delegator", version: "1.0.0" };
|
|
36
41
|
const DEFAULT_CONTEXT_BYTES = 32 * 1024;
|
|
37
42
|
const DEFAULT_ROUTE_TIMEOUT_SEC = 120;
|
|
38
43
|
const DIRECT_PROGRESS_START = 5;
|
|
39
44
|
const DIRECT_PROGRESS_RUNNING = 60;
|
|
40
45
|
const DIRECT_PROGRESS_DONE = 100;
|
|
41
|
-
const SEARCH_ENGINE_CACHE_PATH = [
|
|
46
|
+
const SEARCH_ENGINE_CACHE_PATH = [".omc", "state", "search-engines.json"];
|
|
42
47
|
|
|
43
48
|
const AGENT_TIMEOUT_SEC = Object.freeze({
|
|
44
49
|
executor: 1080,
|
|
45
|
-
|
|
50
|
+
"build-fixer": 540,
|
|
46
51
|
debugger: 900,
|
|
47
|
-
|
|
52
|
+
"deep-executor": 3600,
|
|
48
53
|
architect: 3600,
|
|
49
54
|
planner: 3600,
|
|
50
55
|
critic: 3600,
|
|
51
56
|
analyst: 3600,
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
57
|
+
"code-reviewer": 1800,
|
|
58
|
+
"security-reviewer": 1800,
|
|
59
|
+
"quality-reviewer": 1800,
|
|
55
60
|
scientist: 1440,
|
|
56
|
-
|
|
57
|
-
|
|
61
|
+
"scientist-deep": 3600,
|
|
62
|
+
"document-specialist": 1440,
|
|
58
63
|
designer: 900,
|
|
59
64
|
writer: 900,
|
|
60
65
|
explore: 300,
|
|
61
66
|
verifier: 1200,
|
|
62
|
-
|
|
63
|
-
|
|
67
|
+
"test-engineer": 300,
|
|
68
|
+
"qa-tester": 300,
|
|
64
69
|
spark: 180,
|
|
65
70
|
});
|
|
66
71
|
|
|
67
72
|
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:
|
|
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",
|
|
86
91
|
});
|
|
87
92
|
|
|
88
93
|
const GEMINI_MODEL_BY_AGENT = Object.freeze({
|
|
89
|
-
|
|
90
|
-
writer:
|
|
91
|
-
spark:
|
|
94
|
+
"build-fixer": "gemini-3-flash-preview",
|
|
95
|
+
writer: "gemini-3-flash-preview",
|
|
96
|
+
spark: "gemini-3-flash-preview",
|
|
92
97
|
});
|
|
93
98
|
|
|
94
99
|
const REVIEW_INSTRUCTION_BY_AGENT = Object.freeze({
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
100
|
+
"code-reviewer":
|
|
101
|
+
"코드 리뷰 모드로 동작하라. 버그, 리스크, 회귀, 테스트 누락을 우선 식별하라.",
|
|
102
|
+
"security-reviewer":
|
|
103
|
+
"보안 리뷰 모드로 동작하라. 취약점, 권한 경계, 비밀정보 노출 가능성을 우선 식별하라.",
|
|
104
|
+
"quality-reviewer":
|
|
105
|
+
"품질 리뷰 모드로 동작하라. 로직 결함, 유지보수성 저하, 테스트 누락을 우선 식별하라.",
|
|
98
106
|
});
|
|
99
107
|
|
|
100
108
|
function cloneEnv(env = process.env) {
|
|
101
109
|
return Object.fromEntries(
|
|
102
|
-
Object.entries(env).filter(([, value]) => typeof value ===
|
|
110
|
+
Object.entries(env).filter(([, value]) => typeof value === "string"),
|
|
103
111
|
);
|
|
104
112
|
}
|
|
105
113
|
|
|
@@ -108,7 +116,7 @@ function parseJsonArray(raw, fallback = []) {
|
|
|
108
116
|
try {
|
|
109
117
|
const parsed = JSON.parse(raw);
|
|
110
118
|
return Array.isArray(parsed)
|
|
111
|
-
? parsed.map((item) => String(item ??
|
|
119
|
+
? parsed.map((item) => String(item ?? "")).filter(Boolean)
|
|
112
120
|
: [...fallback];
|
|
113
121
|
} catch {
|
|
114
122
|
return [...fallback];
|
|
@@ -117,7 +125,9 @@ function parseJsonArray(raw, fallback = []) {
|
|
|
117
125
|
|
|
118
126
|
function resolveCandidatePath(candidate, cwd = process.cwd()) {
|
|
119
127
|
if (!candidate) return null;
|
|
120
|
-
const normalized = isAbsolute(candidate)
|
|
128
|
+
const normalized = isAbsolute(candidate)
|
|
129
|
+
? candidate
|
|
130
|
+
: resolve(cwd, candidate);
|
|
121
131
|
return existsSync(normalized) ? normalized : null;
|
|
122
132
|
}
|
|
123
133
|
|
|
@@ -126,8 +136,8 @@ function resolveRouteScript(explicitPath, cwd = process.cwd()) {
|
|
|
126
136
|
explicitPath,
|
|
127
137
|
process.env.TFX_DELEGATOR_ROUTE_SCRIPT,
|
|
128
138
|
process.env.TFX_ROUTE_SCRIPT,
|
|
129
|
-
resolve(SCRIPT_DIR,
|
|
130
|
-
resolve(cwd,
|
|
139
|
+
resolve(SCRIPT_DIR, "..", "..", "scripts", "tfx-route.sh"),
|
|
140
|
+
resolve(cwd, "scripts", "tfx-route.sh"),
|
|
131
141
|
];
|
|
132
142
|
|
|
133
143
|
for (const candidate of candidates) {
|
|
@@ -139,11 +149,11 @@ function resolveRouteScript(explicitPath, cwd = process.cwd()) {
|
|
|
139
149
|
}
|
|
140
150
|
|
|
141
151
|
function resolveCodexProfile(agentType) {
|
|
142
|
-
return CODEX_PROFILE_BY_AGENT[agentType] ||
|
|
152
|
+
return CODEX_PROFILE_BY_AGENT[agentType] || "high";
|
|
143
153
|
}
|
|
144
154
|
|
|
145
155
|
function resolveGeminiModel(agentType) {
|
|
146
|
-
return GEMINI_MODEL_BY_AGENT[agentType] ||
|
|
156
|
+
return GEMINI_MODEL_BY_AGENT[agentType] || "gemini-3.1-pro-preview";
|
|
147
157
|
}
|
|
148
158
|
|
|
149
159
|
function resolveTimeoutMs(agentType, timeoutMs) {
|
|
@@ -161,13 +171,13 @@ function resolveTimeoutSec(agentType, timeoutMs) {
|
|
|
161
171
|
}
|
|
162
172
|
|
|
163
173
|
function loadContextFromFile(contextFile) {
|
|
164
|
-
if (!contextFile) return
|
|
174
|
+
if (!contextFile) return "";
|
|
165
175
|
const resolved = resolveCandidatePath(contextFile);
|
|
166
|
-
if (!resolved) return
|
|
176
|
+
if (!resolved) return "";
|
|
167
177
|
try {
|
|
168
|
-
return readFileSync(resolved,
|
|
178
|
+
return readFileSync(resolved, "utf8").slice(0, DEFAULT_CONTEXT_BYTES);
|
|
169
179
|
} catch {
|
|
170
|
-
return
|
|
180
|
+
return "";
|
|
171
181
|
}
|
|
172
182
|
}
|
|
173
183
|
|
|
@@ -183,7 +193,9 @@ function withPromptHint(prompt, args) {
|
|
|
183
193
|
agentType: args.agentType,
|
|
184
194
|
requestedProfile: args.mcpProfile,
|
|
185
195
|
searchTool: args.searchTool,
|
|
186
|
-
workerIndex: Number.isInteger(args.workerIndex)
|
|
196
|
+
workerIndex: Number.isInteger(args.workerIndex)
|
|
197
|
+
? args.workerIndex
|
|
198
|
+
: undefined,
|
|
187
199
|
taskText: promptWithContext,
|
|
188
200
|
});
|
|
189
201
|
if (!hint) return promptWithContext;
|
|
@@ -192,8 +204,8 @@ function withPromptHint(prompt, args) {
|
|
|
192
204
|
|
|
193
205
|
function joinInstructions(...values) {
|
|
194
206
|
return values
|
|
195
|
-
.filter((value) => typeof value ===
|
|
196
|
-
.join(
|
|
207
|
+
.filter((value) => typeof value === "string" && value.trim())
|
|
208
|
+
.join("\n")
|
|
197
209
|
.trim();
|
|
198
210
|
}
|
|
199
211
|
|
|
@@ -202,36 +214,40 @@ function _loadAvailableServersFromSearchEngineCache(cwd = process.cwd()) {
|
|
|
202
214
|
if (!existsSync(cacheFile)) return null;
|
|
203
215
|
|
|
204
216
|
try {
|
|
205
|
-
const parsed = JSON.parse(readFileSync(cacheFile,
|
|
217
|
+
const parsed = JSON.parse(readFileSync(cacheFile, "utf8"));
|
|
206
218
|
if (!Array.isArray(parsed?.engines)) return null;
|
|
207
219
|
return parsed.engines
|
|
208
|
-
.filter((engine) => engine?.status ===
|
|
209
|
-
.map((engine) =>
|
|
220
|
+
.filter((engine) => engine?.status === "available")
|
|
221
|
+
.map((engine) =>
|
|
222
|
+
typeof engine?.name === "string" ? engine.name.trim() : "",
|
|
223
|
+
)
|
|
210
224
|
.filter(Boolean);
|
|
211
225
|
} catch {
|
|
212
226
|
return null;
|
|
213
227
|
}
|
|
214
228
|
}
|
|
215
229
|
|
|
216
|
-
function parseRouteType(stderr =
|
|
230
|
+
function parseRouteType(stderr = "") {
|
|
217
231
|
const match = stderr.match(/type=([a-z-]+)/);
|
|
218
232
|
if (!match) return null;
|
|
219
|
-
if (match[1] ===
|
|
220
|
-
if (match[1] ===
|
|
233
|
+
if (match[1] === "codex") return "codex";
|
|
234
|
+
if (match[1] === "gemini") return "gemini";
|
|
221
235
|
return match[1];
|
|
222
236
|
}
|
|
223
237
|
|
|
224
238
|
function summarizePayload(payload) {
|
|
225
|
-
if (typeof payload.output ===
|
|
226
|
-
|
|
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}`;
|
|
227
243
|
if (payload.job_id) return `jobId=${payload.job_id} 상태=${payload.status}`;
|
|
228
244
|
if (payload.status) return `상태=${payload.status}`;
|
|
229
|
-
return payload.ok ?
|
|
245
|
+
return payload.ok ? "완료되었습니다." : "실패했습니다.";
|
|
230
246
|
}
|
|
231
247
|
|
|
232
248
|
function createToolResponse(payload, { isError = false } = {}) {
|
|
233
249
|
return {
|
|
234
|
-
content: [{ type:
|
|
250
|
+
content: [{ type: "text", text: summarizePayload(payload) }],
|
|
235
251
|
structuredContent: payload,
|
|
236
252
|
isError,
|
|
237
253
|
};
|
|
@@ -240,55 +256,107 @@ function createToolResponse(payload, { isError = false } = {}) {
|
|
|
240
256
|
function createErrorPayload(message, extras = {}) {
|
|
241
257
|
return {
|
|
242
258
|
ok: false,
|
|
243
|
-
status:
|
|
259
|
+
status: "failed",
|
|
244
260
|
error: message,
|
|
245
261
|
...extras,
|
|
246
262
|
};
|
|
247
263
|
}
|
|
248
264
|
|
|
249
265
|
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
|
-
|
|
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 기반 병렬 실행"),
|
|
274
339
|
});
|
|
275
340
|
|
|
276
341
|
const DelegateStatusInputSchema = z.object({
|
|
277
|
-
jobId: z.string().min(1).describe(
|
|
342
|
+
jobId: z.string().min(1).describe("조회할 비동기 job ID"),
|
|
278
343
|
});
|
|
279
344
|
|
|
280
345
|
const DelegateReplyInputSchema = z.object({
|
|
281
|
-
job_id: z.string().min(1).describe(
|
|
282
|
-
reply: z.string().min(1).describe(
|
|
283
|
-
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이면 응답 처리 후 대화를 종료"),
|
|
284
352
|
});
|
|
285
353
|
|
|
286
354
|
const DelegateOutputSchema = z.object({
|
|
287
355
|
ok: z.boolean(),
|
|
288
356
|
jobId: z.string().optional(),
|
|
289
357
|
job_id: z.string().optional(),
|
|
290
|
-
mode: z.enum([
|
|
291
|
-
status: z.enum([
|
|
358
|
+
mode: z.enum(["sync", "async"]).optional(),
|
|
359
|
+
status: z.enum(["running", "completed", "failed", "partial"]).optional(),
|
|
292
360
|
error: z.string().optional(),
|
|
293
361
|
providerRequested: z.string().optional(),
|
|
294
362
|
providerResolved: z.string().nullable().optional(),
|
|
@@ -304,33 +372,51 @@ const DelegateOutputSchema = z.object({
|
|
|
304
372
|
threadId: z.string().nullable().optional(),
|
|
305
373
|
sessionKey: z.string().nullable().optional(),
|
|
306
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 세션명"),
|
|
307
389
|
});
|
|
308
390
|
|
|
309
391
|
function isTeamRouteRequested(args) {
|
|
310
392
|
return Boolean(
|
|
311
|
-
args.teamName
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
393
|
+
args.teamName ||
|
|
394
|
+
args.teamTaskId ||
|
|
395
|
+
args.teamAgentName ||
|
|
396
|
+
args.teamLeadName ||
|
|
397
|
+
args.hubUrl,
|
|
316
398
|
);
|
|
317
399
|
}
|
|
318
400
|
|
|
319
401
|
function pickRouteMode(provider) {
|
|
320
|
-
return provider ===
|
|
402
|
+
return provider === "auto" ? "auto" : provider;
|
|
321
403
|
}
|
|
322
404
|
|
|
323
405
|
function sanitizeDelegateArgs(args = {}) {
|
|
324
406
|
return {
|
|
325
|
-
provider: args.provider ||
|
|
326
|
-
agentType: args.agentType ||
|
|
407
|
+
provider: args.provider || "auto",
|
|
408
|
+
agentType: args.agentType || "executor",
|
|
327
409
|
cwd: args.cwd || null,
|
|
328
|
-
timeoutMs: Number.isFinite(Number(args.timeoutMs))
|
|
410
|
+
timeoutMs: Number.isFinite(Number(args.timeoutMs))
|
|
411
|
+
? Math.trunc(Number(args.timeoutMs))
|
|
412
|
+
: null,
|
|
329
413
|
sessionKey: args.sessionKey || null,
|
|
330
414
|
resetSession: Boolean(args.resetSession),
|
|
331
|
-
mcpProfile: args.mcpProfile ||
|
|
415
|
+
mcpProfile: args.mcpProfile || "auto",
|
|
332
416
|
contextFile: args.contextFile || null,
|
|
333
|
-
availableServers: Array.isArray(args.availableServers)
|
|
417
|
+
availableServers: Array.isArray(args.availableServers)
|
|
418
|
+
? args.availableServers
|
|
419
|
+
: null,
|
|
334
420
|
searchTool: args.searchTool || null,
|
|
335
421
|
workerIndex: Number.isInteger(args.workerIndex) ? args.workerIndex : null,
|
|
336
422
|
model: args.model || null,
|
|
@@ -348,21 +434,21 @@ function sanitizeDelegateArgs(args = {}) {
|
|
|
348
434
|
}
|
|
349
435
|
|
|
350
436
|
function formatConversationTranscript(turns = []) {
|
|
351
|
-
return turns
|
|
352
|
-
|
|
353
|
-
`Turn ${index + 1} user:\n${turn.user}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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");
|
|
360
446
|
}
|
|
361
447
|
|
|
362
448
|
async function emitProgress(extra, progress, total, message) {
|
|
363
449
|
if (extra?._meta?.progressToken === undefined) return;
|
|
364
450
|
await extra.sendNotification({
|
|
365
|
-
method:
|
|
451
|
+
method: "notifications/progress",
|
|
366
452
|
params: {
|
|
367
453
|
progressToken: extra._meta.progressToken,
|
|
368
454
|
progress,
|
|
@@ -373,34 +459,39 @@ async function emitProgress(extra, progress, total, message) {
|
|
|
373
459
|
}
|
|
374
460
|
|
|
375
461
|
export class DelegatorMcpWorker {
|
|
376
|
-
type =
|
|
462
|
+
type = "delegator";
|
|
377
463
|
|
|
378
464
|
constructor(options = {}) {
|
|
379
465
|
this.cwd = options.cwd || process.cwd();
|
|
380
466
|
this.env = cloneEnv({ ...cloneEnv(process.env), ...cloneEnv(options.env) });
|
|
381
467
|
this.routeScript = resolveRouteScript(options.routeScript, this.cwd);
|
|
382
|
-
this.bashCommand =
|
|
383
|
-
||
|
|
384
|
-
|
|
385
|
-
||
|
|
468
|
+
this.bashCommand =
|
|
469
|
+
options.bashCommand ||
|
|
470
|
+
this.env.TFX_DELEGATOR_BASH_COMMAND ||
|
|
471
|
+
this.env.BASH_BIN ||
|
|
472
|
+
resolveBashExecutable();
|
|
386
473
|
|
|
387
474
|
this.codexWorker = new CodexMcpWorker({
|
|
388
|
-
command:
|
|
389
|
-
||
|
|
390
|
-
|
|
391
|
-
||
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
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, []),
|
|
395
484
|
cwd: this.cwd,
|
|
396
485
|
env: this.env,
|
|
397
486
|
clientInfo: { name: SERVER_INFO.name, version: SERVER_INFO.version },
|
|
398
487
|
});
|
|
399
488
|
|
|
400
|
-
this.geminiCommand =
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
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, []);
|
|
404
495
|
|
|
405
496
|
this.server = null;
|
|
406
497
|
this.transport = null;
|
|
@@ -424,32 +515,48 @@ export class DelegatorMcpWorker {
|
|
|
424
515
|
capabilities: { logging: {} },
|
|
425
516
|
});
|
|
426
517
|
|
|
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
|
-
|
|
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
|
+
);
|
|
453
560
|
|
|
454
561
|
this.server = server;
|
|
455
562
|
this.ready = true;
|
|
@@ -467,7 +574,9 @@ export class DelegatorMcpWorker {
|
|
|
467
574
|
this.ready = false;
|
|
468
575
|
|
|
469
576
|
for (const child of this.routeChildren) {
|
|
470
|
-
try {
|
|
577
|
+
try {
|
|
578
|
+
child.kill();
|
|
579
|
+
} catch {}
|
|
471
580
|
}
|
|
472
581
|
this.routeChildren.clear();
|
|
473
582
|
|
|
@@ -496,7 +605,7 @@ export class DelegatorMcpWorker {
|
|
|
496
605
|
async execute(prompt, options = {}) {
|
|
497
606
|
const result = await this._executeDirect({ prompt, ...options });
|
|
498
607
|
return {
|
|
499
|
-
output: result.output || result.error ||
|
|
608
|
+
output: result.output || result.error || "",
|
|
500
609
|
exitCode: result.exitCode ?? (result.ok ? 0 : 1),
|
|
501
610
|
threadId: result.threadId || null,
|
|
502
611
|
sessionKey: result.sessionKey || null,
|
|
@@ -505,7 +614,7 @@ export class DelegatorMcpWorker {
|
|
|
505
614
|
}
|
|
506
615
|
|
|
507
616
|
async delegate(args, extra) {
|
|
508
|
-
if (args.mode ===
|
|
617
|
+
if (args.mode === "async") {
|
|
509
618
|
return this._startAsyncJob(args, extra);
|
|
510
619
|
}
|
|
511
620
|
return this._runSyncJob(args, extra);
|
|
@@ -518,11 +627,11 @@ export class DelegatorMcpWorker {
|
|
|
518
627
|
}
|
|
519
628
|
|
|
520
629
|
const payload = this._serializeJob(job);
|
|
521
|
-
if (job.status ===
|
|
630
|
+
if (job.status === "running") {
|
|
522
631
|
await emitProgress(extra, 25, 100, `job ${jobId} 실행 중`);
|
|
523
|
-
} else if (job.status ===
|
|
632
|
+
} else if (job.status === "completed") {
|
|
524
633
|
await emitProgress(extra, DIRECT_PROGRESS_DONE, 100, `job ${jobId} 완료`);
|
|
525
|
-
} else if (job.status ===
|
|
634
|
+
} else if (job.status === "failed") {
|
|
526
635
|
await emitProgress(extra, DIRECT_PROGRESS_DONE, 100, `job ${jobId} 실패`);
|
|
527
636
|
}
|
|
528
637
|
return payload;
|
|
@@ -531,28 +640,51 @@ export class DelegatorMcpWorker {
|
|
|
531
640
|
async reply({ job_id, reply, done = false }, extra) {
|
|
532
641
|
const job = this.jobs.get(job_id);
|
|
533
642
|
if (!job) {
|
|
534
|
-
return createErrorPayload(`알 수 없는 jobId: ${job_id}`, {
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
643
|
+
return createErrorPayload(`알 수 없는 jobId: ${job_id}`, {
|
|
644
|
+
jobId: job_id,
|
|
645
|
+
job_id,
|
|
646
|
+
});
|
|
538
647
|
}
|
|
539
|
-
if (job.
|
|
540
|
-
return createErrorPayload(
|
|
648
|
+
if (job.status === "running") {
|
|
649
|
+
return createErrorPayload(`job ${job_id}가 아직 실행 중입니다.`, {
|
|
541
650
|
jobId: job_id,
|
|
542
651
|
job_id,
|
|
543
652
|
});
|
|
544
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
|
+
}
|
|
545
666
|
|
|
546
667
|
const conversation = this.geminiConversations.get(job_id);
|
|
547
668
|
if (!conversation) {
|
|
548
|
-
return createErrorPayload(`Gemini 대화 컨텍스트가 없습니다: ${job_id}`, {
|
|
669
|
+
return createErrorPayload(`Gemini 대화 컨텍스트가 없습니다: ${job_id}`, {
|
|
670
|
+
jobId: job_id,
|
|
671
|
+
job_id,
|
|
672
|
+
});
|
|
549
673
|
}
|
|
550
674
|
if (conversation.closed) {
|
|
551
|
-
return createErrorPayload(`이미 종료된 대화입니다: ${job_id}`, {
|
|
675
|
+
return createErrorPayload(`이미 종료된 대화입니다: ${job_id}`, {
|
|
676
|
+
jobId: job_id,
|
|
677
|
+
job_id,
|
|
678
|
+
});
|
|
552
679
|
}
|
|
553
680
|
|
|
554
|
-
await emitProgress(
|
|
555
|
-
|
|
681
|
+
await emitProgress(
|
|
682
|
+
extra,
|
|
683
|
+
DIRECT_PROGRESS_START,
|
|
684
|
+
100,
|
|
685
|
+
`job ${job_id} 후속 응답을 시작합니다.`,
|
|
686
|
+
);
|
|
687
|
+
job.status = "running";
|
|
556
688
|
job.updatedAt = new Date().toISOString();
|
|
557
689
|
|
|
558
690
|
const worker = this._createGeminiWorker();
|
|
@@ -564,8 +696,10 @@ export class DelegatorMcpWorker {
|
|
|
564
696
|
cwd: job.requestArgs.cwd || this.cwd,
|
|
565
697
|
timeoutMs: resolveTimeoutMs(job.agentType, job.requestArgs.timeoutMs),
|
|
566
698
|
model: job.requestArgs.model || resolveGeminiModel(job.agentType),
|
|
567
|
-
approvalMode:
|
|
568
|
-
allowedMcpServerNames: getGeminiAllowedServers(
|
|
699
|
+
approvalMode: "yolo",
|
|
700
|
+
allowedMcpServerNames: getGeminiAllowedServers(
|
|
701
|
+
this._getMcpPolicyOptions(job.requestArgs),
|
|
702
|
+
),
|
|
569
703
|
});
|
|
570
704
|
|
|
571
705
|
conversation.turns.push({
|
|
@@ -580,26 +714,34 @@ export class DelegatorMcpWorker {
|
|
|
580
714
|
|
|
581
715
|
this._applyJobResult(job, {
|
|
582
716
|
ok: result.exitCode === 0,
|
|
583
|
-
status: result.exitCode === 0 ?
|
|
584
|
-
providerRequested:
|
|
585
|
-
providerResolved:
|
|
717
|
+
status: result.exitCode === 0 ? "completed" : "failed",
|
|
718
|
+
providerRequested: "gemini",
|
|
719
|
+
providerResolved: "gemini",
|
|
586
720
|
agentType: job.agentType,
|
|
587
|
-
transport:
|
|
721
|
+
transport: "gemini-worker",
|
|
588
722
|
exitCode: result.exitCode,
|
|
589
723
|
output: result.output,
|
|
590
724
|
sessionKey: result.sessionKey || job.sessionKey || null,
|
|
591
725
|
});
|
|
592
|
-
await emitProgress(
|
|
726
|
+
await emitProgress(
|
|
727
|
+
extra,
|
|
728
|
+
DIRECT_PROGRESS_DONE,
|
|
729
|
+
100,
|
|
730
|
+
`job ${job_id} 후속 응답이 완료되었습니다.`,
|
|
731
|
+
);
|
|
593
732
|
return this._serializeJob(job);
|
|
594
733
|
} catch (error) {
|
|
595
734
|
const message = error instanceof Error ? error.message : String(error);
|
|
596
|
-
this._applyJobResult(
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
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
|
+
);
|
|
603
745
|
return this._serializeJob(job);
|
|
604
746
|
} finally {
|
|
605
747
|
await worker.stop().catch(() => {});
|
|
@@ -608,8 +750,9 @@ export class DelegatorMcpWorker {
|
|
|
608
750
|
}
|
|
609
751
|
|
|
610
752
|
_createGeminiWorker() {
|
|
753
|
+
const resolved = whichCommand(this.geminiCommand) || this.geminiCommand;
|
|
611
754
|
return new GeminiWorker({
|
|
612
|
-
command:
|
|
755
|
+
command: resolved,
|
|
613
756
|
commandArgs: this.geminiCommandArgs,
|
|
614
757
|
cwd: this.cwd,
|
|
615
758
|
env: this.env,
|
|
@@ -617,15 +760,17 @@ export class DelegatorMcpWorker {
|
|
|
617
760
|
}
|
|
618
761
|
|
|
619
762
|
_buildDirectPrompt(args) {
|
|
620
|
-
return withContext(String(args.prompt ??
|
|
763
|
+
return withContext(String(args.prompt ?? ""), args.contextFile);
|
|
621
764
|
}
|
|
622
765
|
|
|
623
766
|
_buildDirectPromptWithHint(args) {
|
|
624
|
-
return withPromptHint(String(args.prompt ??
|
|
625
|
-
agentType: args.agentType ||
|
|
626
|
-
mcpProfile: args.mcpProfile ||
|
|
767
|
+
return withPromptHint(String(args.prompt ?? ""), {
|
|
768
|
+
agentType: args.agentType || "executor",
|
|
769
|
+
mcpProfile: args.mcpProfile || "auto",
|
|
627
770
|
searchTool: args.searchTool,
|
|
628
|
-
workerIndex: Number.isInteger(args.workerIndex)
|
|
771
|
+
workerIndex: Number.isInteger(args.workerIndex)
|
|
772
|
+
? args.workerIndex
|
|
773
|
+
: undefined,
|
|
629
774
|
contextFile: args.contextFile,
|
|
630
775
|
});
|
|
631
776
|
}
|
|
@@ -633,26 +778,30 @@ export class DelegatorMcpWorker {
|
|
|
633
778
|
_buildGeminiReplyPrompt(conversation, reply) {
|
|
634
779
|
const transcript = formatConversationTranscript(conversation.turns);
|
|
635
780
|
return [
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
781
|
+
"Continue the conversation using the prior transcript below.",
|
|
782
|
+
"",
|
|
783
|
+
"<conversation_history>",
|
|
639
784
|
transcript,
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
785
|
+
"</conversation_history>",
|
|
786
|
+
"",
|
|
787
|
+
"<latest_user_reply>",
|
|
643
788
|
reply,
|
|
644
|
-
|
|
645
|
-
].join(
|
|
789
|
+
"</latest_user_reply>",
|
|
790
|
+
].join("\n");
|
|
646
791
|
}
|
|
647
792
|
|
|
648
793
|
_getMcpPolicyOptions(args) {
|
|
649
794
|
return {
|
|
650
|
-
agentType: args.agentType ||
|
|
651
|
-
requestedProfile: args.mcpProfile ||
|
|
795
|
+
agentType: args.agentType || "executor",
|
|
796
|
+
requestedProfile: args.mcpProfile || "auto",
|
|
652
797
|
searchTool: args.searchTool,
|
|
653
|
-
workerIndex: Number.isInteger(args.workerIndex)
|
|
654
|
-
|
|
655
|
-
|
|
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,
|
|
656
805
|
};
|
|
657
806
|
}
|
|
658
807
|
|
|
@@ -661,11 +810,38 @@ export class DelegatorMcpWorker {
|
|
|
661
810
|
}
|
|
662
811
|
|
|
663
812
|
_shouldUseRoute(args) {
|
|
664
|
-
return args.provider ===
|
|
813
|
+
return args.provider === "auto" || isTeamRouteRequested(args);
|
|
665
814
|
}
|
|
666
815
|
|
|
667
816
|
async _executeDirect(args, extra = null) {
|
|
668
|
-
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
|
+
}
|
|
669
845
|
|
|
670
846
|
const runViaRoute = this._shouldUseRoute(args);
|
|
671
847
|
|
|
@@ -674,50 +850,152 @@ export class DelegatorMcpWorker {
|
|
|
674
850
|
? await this._executeRoute(args, extra)
|
|
675
851
|
: await this._executeWorker(args, extra);
|
|
676
852
|
|
|
677
|
-
await emitProgress(
|
|
853
|
+
await emitProgress(
|
|
854
|
+
extra,
|
|
855
|
+
DIRECT_PROGRESS_DONE,
|
|
856
|
+
100,
|
|
857
|
+
"위임이 완료되었습니다.",
|
|
858
|
+
);
|
|
678
859
|
return result;
|
|
679
860
|
} catch (error) {
|
|
680
861
|
const message = error instanceof Error ? error.message : String(error);
|
|
681
862
|
return createErrorPayload(message, {
|
|
682
|
-
mode:
|
|
863
|
+
mode: "sync",
|
|
683
864
|
providerRequested: args.provider,
|
|
684
865
|
agentType: args.agentType,
|
|
685
|
-
transport: runViaRoute ?
|
|
866
|
+
transport: runViaRoute ? "route-script" : `${args.provider}-worker`,
|
|
686
867
|
});
|
|
687
868
|
}
|
|
688
869
|
}
|
|
689
870
|
|
|
690
|
-
async
|
|
691
|
-
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
|
+
});
|
|
692
888
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
threadId: args.threadId,
|
|
699
|
-
resetSession: args.resetSession,
|
|
700
|
-
profile: resolveCodexProfile(args.agentType),
|
|
701
|
-
sandbox: 'danger-full-access',
|
|
702
|
-
approvalPolicy: 'never',
|
|
703
|
-
developerInstructions: joinInstructions(
|
|
704
|
-
REVIEW_INSTRUCTION_BY_AGENT[args.agentType],
|
|
705
|
-
this._buildPromptHintInstruction(args),
|
|
706
|
-
args.developerInstructions,
|
|
889
|
+
// 타임아웃: 워커 중 가장 긴 타임아웃 사용
|
|
890
|
+
const maxTimeoutSec = Math.max(
|
|
891
|
+
...args.workers.map((w) =>
|
|
892
|
+
Math.ceil(
|
|
893
|
+
resolveTimeoutMs(w.agentType || "executor", args.timeoutMs) / 1000,
|
|
707
894
|
),
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
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",
|
|
711
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
|
+
);
|
|
712
990
|
|
|
713
991
|
return {
|
|
714
992
|
ok: result.exitCode === 0,
|
|
715
|
-
mode:
|
|
716
|
-
status: result.exitCode === 0 ?
|
|
717
|
-
providerRequested:
|
|
718
|
-
providerResolved:
|
|
993
|
+
mode: "sync",
|
|
994
|
+
status: result.exitCode === 0 ? "completed" : "failed",
|
|
995
|
+
providerRequested: "codex",
|
|
996
|
+
providerResolved: "codex",
|
|
719
997
|
agentType: args.agentType,
|
|
720
|
-
transport:
|
|
998
|
+
transport: "codex-mcp",
|
|
721
999
|
exitCode: result.exitCode,
|
|
722
1000
|
output: result.output,
|
|
723
1001
|
sessionKey: result.sessionKey,
|
|
@@ -725,7 +1003,7 @@ export class DelegatorMcpWorker {
|
|
|
725
1003
|
};
|
|
726
1004
|
}
|
|
727
1005
|
|
|
728
|
-
if (args.provider ===
|
|
1006
|
+
if (args.provider === "gemini") {
|
|
729
1007
|
const worker = this._createGeminiWorker();
|
|
730
1008
|
const prompt = this._buildDirectPromptWithHint(args);
|
|
731
1009
|
try {
|
|
@@ -733,18 +1011,20 @@ export class DelegatorMcpWorker {
|
|
|
733
1011
|
cwd: args.cwd || this.cwd,
|
|
734
1012
|
timeoutMs: resolveTimeoutMs(args.agentType, args.timeoutMs),
|
|
735
1013
|
model: args.model || resolveGeminiModel(args.agentType),
|
|
736
|
-
approvalMode:
|
|
737
|
-
allowedMcpServerNames: getGeminiAllowedServers(
|
|
1014
|
+
approvalMode: "yolo",
|
|
1015
|
+
allowedMcpServerNames: getGeminiAllowedServers(
|
|
1016
|
+
this._getMcpPolicyOptions(args),
|
|
1017
|
+
),
|
|
738
1018
|
});
|
|
739
1019
|
|
|
740
1020
|
return {
|
|
741
1021
|
ok: result.exitCode === 0,
|
|
742
|
-
mode:
|
|
743
|
-
status: result.exitCode === 0 ?
|
|
744
|
-
providerRequested:
|
|
745
|
-
providerResolved:
|
|
1022
|
+
mode: "sync",
|
|
1023
|
+
status: result.exitCode === 0 ? "completed" : "failed",
|
|
1024
|
+
providerRequested: "gemini",
|
|
1025
|
+
providerResolved: "gemini",
|
|
746
1026
|
agentType: args.agentType,
|
|
747
|
-
transport:
|
|
1027
|
+
transport: "gemini-worker",
|
|
748
1028
|
exitCode: result.exitCode,
|
|
749
1029
|
output: result.output,
|
|
750
1030
|
sessionKey: result.sessionKey,
|
|
@@ -755,34 +1035,42 @@ export class DelegatorMcpWorker {
|
|
|
755
1035
|
}
|
|
756
1036
|
}
|
|
757
1037
|
|
|
758
|
-
return createErrorPayload(
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
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
|
+
);
|
|
764
1047
|
}
|
|
765
1048
|
|
|
766
1049
|
async _executeRoute(args, extra) {
|
|
767
1050
|
if (!this.routeScript) {
|
|
768
|
-
return createErrorPayload(
|
|
769
|
-
mode:
|
|
1051
|
+
return createErrorPayload("tfx-route.sh 경로를 찾지 못했습니다.", {
|
|
1052
|
+
mode: "sync",
|
|
770
1053
|
providerRequested: args.provider,
|
|
771
1054
|
agentType: args.agentType,
|
|
772
|
-
transport:
|
|
1055
|
+
transport: "route-script",
|
|
773
1056
|
});
|
|
774
1057
|
}
|
|
775
1058
|
|
|
776
|
-
await emitProgress(
|
|
1059
|
+
await emitProgress(
|
|
1060
|
+
extra,
|
|
1061
|
+
DIRECT_PROGRESS_RUNNING,
|
|
1062
|
+
100,
|
|
1063
|
+
"tfx-route.sh 경로로 실행 중입니다.",
|
|
1064
|
+
);
|
|
777
1065
|
const result = await this._spawnRoute(args);
|
|
778
1066
|
return {
|
|
779
1067
|
ok: result.exitCode === 0,
|
|
780
|
-
mode:
|
|
781
|
-
status: result.exitCode === 0 ?
|
|
1068
|
+
mode: "sync",
|
|
1069
|
+
status: result.exitCode === 0 ? "completed" : "failed",
|
|
782
1070
|
providerRequested: args.provider,
|
|
783
1071
|
providerResolved: parseRouteType(result.stderr) || args.provider,
|
|
784
1072
|
agentType: args.agentType,
|
|
785
|
-
transport:
|
|
1073
|
+
transport: "route-script",
|
|
786
1074
|
exitCode: result.exitCode,
|
|
787
1075
|
output: result.stdout.trim() || result.stderr.trim(),
|
|
788
1076
|
stderr: result.stderr.trim(),
|
|
@@ -790,10 +1078,15 @@ export class DelegatorMcpWorker {
|
|
|
790
1078
|
}
|
|
791
1079
|
|
|
792
1080
|
async _startAsyncJob(args, extra) {
|
|
793
|
-
const job = this._createJob(args,
|
|
1081
|
+
const job = this._createJob(args, "async");
|
|
794
1082
|
this.jobs.set(job.jobId, job);
|
|
795
1083
|
|
|
796
|
-
await emitProgress(
|
|
1084
|
+
await emitProgress(
|
|
1085
|
+
extra,
|
|
1086
|
+
DIRECT_PROGRESS_START,
|
|
1087
|
+
100,
|
|
1088
|
+
`비동기 job ${job.jobId}를 시작합니다.`,
|
|
1089
|
+
);
|
|
797
1090
|
|
|
798
1091
|
void (async () => {
|
|
799
1092
|
try {
|
|
@@ -802,15 +1095,20 @@ export class DelegatorMcpWorker {
|
|
|
802
1095
|
: await this._runAsyncWorker(args, job);
|
|
803
1096
|
this._applyJobResult(job, result);
|
|
804
1097
|
} catch (error) {
|
|
805
|
-
this._applyJobResult(
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
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
|
+
);
|
|
814
1112
|
} finally {
|
|
815
1113
|
if (job.worker) {
|
|
816
1114
|
await job.worker.stop().catch(() => {});
|
|
@@ -824,29 +1122,32 @@ export class DelegatorMcpWorker {
|
|
|
824
1122
|
}
|
|
825
1123
|
|
|
826
1124
|
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
|
-
|
|
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
|
+
);
|
|
846
1147
|
|
|
847
1148
|
return {
|
|
848
1149
|
ok: result.exitCode === 0,
|
|
849
|
-
providerResolved:
|
|
1150
|
+
providerResolved: "codex",
|
|
850
1151
|
output: result.output,
|
|
851
1152
|
exitCode: result.exitCode,
|
|
852
1153
|
threadId: result.threadId,
|
|
@@ -854,7 +1155,7 @@ export class DelegatorMcpWorker {
|
|
|
854
1155
|
};
|
|
855
1156
|
}
|
|
856
1157
|
|
|
857
|
-
if (args.provider ===
|
|
1158
|
+
if (args.provider === "gemini") {
|
|
858
1159
|
const worker = this._createGeminiWorker();
|
|
859
1160
|
job.worker = worker;
|
|
860
1161
|
const prompt = this._buildDirectPromptWithHint(args);
|
|
@@ -862,13 +1163,15 @@ export class DelegatorMcpWorker {
|
|
|
862
1163
|
cwd: args.cwd || this.cwd,
|
|
863
1164
|
timeoutMs: resolveTimeoutMs(args.agentType, args.timeoutMs),
|
|
864
1165
|
model: args.model || resolveGeminiModel(args.agentType),
|
|
865
|
-
approvalMode:
|
|
866
|
-
allowedMcpServerNames: getGeminiAllowedServers(
|
|
1166
|
+
approvalMode: "yolo",
|
|
1167
|
+
allowedMcpServerNames: getGeminiAllowedServers(
|
|
1168
|
+
this._getMcpPolicyOptions(args),
|
|
1169
|
+
),
|
|
867
1170
|
});
|
|
868
1171
|
|
|
869
1172
|
return {
|
|
870
1173
|
ok: result.exitCode === 0,
|
|
871
|
-
providerResolved:
|
|
1174
|
+
providerResolved: "gemini",
|
|
872
1175
|
output: result.output,
|
|
873
1176
|
exitCode: result.exitCode,
|
|
874
1177
|
sessionKey: result.sessionKey,
|
|
@@ -880,14 +1183,26 @@ export class DelegatorMcpWorker {
|
|
|
880
1183
|
}
|
|
881
1184
|
|
|
882
1185
|
_buildRouteEnv(args) {
|
|
883
|
-
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 };
|
|
884
1198
|
env.TFX_CLI_MODE = pickRouteMode(args.provider);
|
|
1199
|
+
if (sandbox.root) env.TFX_WORKER_SANDBOX_SCOPE = "delegator-route";
|
|
885
1200
|
|
|
886
1201
|
if (args.codexTransport) {
|
|
887
1202
|
env.TFX_CODEX_TRANSPORT = args.codexTransport;
|
|
888
1203
|
}
|
|
889
1204
|
if (args.noClaudeNative === true) {
|
|
890
|
-
env.TFX_NO_CLAUDE_NATIVE =
|
|
1205
|
+
env.TFX_NO_CLAUDE_NATIVE = "1";
|
|
891
1206
|
}
|
|
892
1207
|
if (args.searchTool) {
|
|
893
1208
|
env.TFX_SEARCH_TOOL = args.searchTool;
|
|
@@ -905,19 +1220,19 @@ export class DelegatorMcpWorker {
|
|
|
905
1220
|
}
|
|
906
1221
|
|
|
907
1222
|
async _spawnRoute(args, job = null) {
|
|
908
|
-
const prompt = withContext(String(args.prompt ??
|
|
1223
|
+
const prompt = withContext(String(args.prompt ?? ""), args.contextFile);
|
|
909
1224
|
const childArgs = [
|
|
910
1225
|
this.routeScript,
|
|
911
|
-
args.agentType ||
|
|
1226
|
+
args.agentType || "executor",
|
|
912
1227
|
prompt,
|
|
913
|
-
args.mcpProfile ||
|
|
1228
|
+
args.mcpProfile || "auto",
|
|
914
1229
|
String(resolveTimeoutSec(args.agentType, args.timeoutMs)),
|
|
915
1230
|
];
|
|
916
1231
|
|
|
917
1232
|
const child = spawn(this.bashCommand, childArgs, {
|
|
918
1233
|
cwd: args.cwd || this.cwd,
|
|
919
1234
|
env: this._buildRouteEnv(args),
|
|
920
|
-
stdio: [
|
|
1235
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
921
1236
|
windowsHide: true,
|
|
922
1237
|
});
|
|
923
1238
|
|
|
@@ -931,16 +1246,16 @@ export class DelegatorMcpWorker {
|
|
|
931
1246
|
const stdoutChunks = [];
|
|
932
1247
|
const stderrChunks = [];
|
|
933
1248
|
|
|
934
|
-
child.stdout.on(
|
|
935
|
-
child.stderr.on(
|
|
936
|
-
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) => {
|
|
937
1252
|
this.routeChildren.delete(child);
|
|
938
1253
|
rejectPromise(error);
|
|
939
1254
|
});
|
|
940
|
-
child.once(
|
|
1255
|
+
child.once("close", (code) => {
|
|
941
1256
|
this.routeChildren.delete(child);
|
|
942
|
-
const stdout = Buffer.concat(stdoutChunks).toString(
|
|
943
|
-
const stderr = Buffer.concat(stderrChunks).toString(
|
|
1257
|
+
const stdout = Buffer.concat(stdoutChunks).toString("utf8");
|
|
1258
|
+
const stderr = Buffer.concat(stderrChunks).toString("utf8");
|
|
944
1259
|
resolvePromise({
|
|
945
1260
|
ok: code === 0,
|
|
946
1261
|
providerResolved: parseRouteType(stderr) || args.provider,
|
|
@@ -957,7 +1272,7 @@ export class DelegatorMcpWorker {
|
|
|
957
1272
|
return {
|
|
958
1273
|
ok: job.ok,
|
|
959
1274
|
job_id: job.jobId,
|
|
960
|
-
mode: job.mode ||
|
|
1275
|
+
mode: job.mode || "async",
|
|
961
1276
|
status: job.status,
|
|
962
1277
|
provider_requested: job.providerRequested,
|
|
963
1278
|
provider_resolved: job.providerResolved,
|
|
@@ -969,7 +1284,7 @@ export class DelegatorMcpWorker {
|
|
|
969
1284
|
completed_at: job.completedAt,
|
|
970
1285
|
output: job.output,
|
|
971
1286
|
stderr: job.stderr,
|
|
972
|
-
error:
|
|
1287
|
+
error: "",
|
|
973
1288
|
thread_id: job.threadId,
|
|
974
1289
|
session_key: job.sessionKey,
|
|
975
1290
|
conversation_open: this.geminiConversations.has(job.jobId),
|
|
@@ -983,17 +1298,19 @@ export class DelegatorMcpWorker {
|
|
|
983
1298
|
ok: true,
|
|
984
1299
|
jobId,
|
|
985
1300
|
mode,
|
|
986
|
-
status:
|
|
1301
|
+
status: "running",
|
|
987
1302
|
providerRequested: args.provider,
|
|
988
1303
|
providerResolved: null,
|
|
989
1304
|
agentType: args.agentType,
|
|
990
|
-
transport: this._shouldUseRoute(args)
|
|
1305
|
+
transport: this._shouldUseRoute(args)
|
|
1306
|
+
? "route-script"
|
|
1307
|
+
: `${args.provider}-worker`,
|
|
991
1308
|
createdAt: now,
|
|
992
1309
|
startedAt: now,
|
|
993
1310
|
updatedAt: now,
|
|
994
1311
|
completedAt: null,
|
|
995
|
-
output:
|
|
996
|
-
stderr:
|
|
1312
|
+
output: "",
|
|
1313
|
+
stderr: "",
|
|
997
1314
|
exitCode: null,
|
|
998
1315
|
threadId: null,
|
|
999
1316
|
sessionKey: args.sessionKey || null,
|
|
@@ -1005,28 +1322,34 @@ export class DelegatorMcpWorker {
|
|
|
1005
1322
|
|
|
1006
1323
|
_applyJobResult(job, result = {}) {
|
|
1007
1324
|
job.ok = result.ok !== false;
|
|
1008
|
-
job.status = job.ok ?
|
|
1325
|
+
job.status = job.ok ? "completed" : "failed";
|
|
1009
1326
|
job.providerResolved = result.providerResolved || job.providerRequested;
|
|
1010
1327
|
job.transport = result.transport || job.transport;
|
|
1011
|
-
job.output = result.output ||
|
|
1012
|
-
job.stderr = result.stderr || result.error ||
|
|
1328
|
+
job.output = result.output || "";
|
|
1329
|
+
job.stderr = result.stderr || result.error || "";
|
|
1013
1330
|
job.exitCode = result.exitCode ?? (job.ok ? 0 : 1);
|
|
1014
1331
|
job.threadId = result.threadId || job.threadId || null;
|
|
1015
1332
|
job.sessionKey = result.sessionKey || job.sessionKey || null;
|
|
1016
1333
|
job.completedAt = new Date().toISOString();
|
|
1017
1334
|
job.updatedAt = job.completedAt;
|
|
1018
1335
|
|
|
1019
|
-
if (
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
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
|
+
);
|
|
1023
1346
|
}
|
|
1024
1347
|
}
|
|
1025
1348
|
|
|
1026
1349
|
_storeGeminiConversation(job, userPrompt, assistantReply) {
|
|
1027
1350
|
const existing = this.geminiConversations.get(job.jobId);
|
|
1028
1351
|
if (existing) {
|
|
1029
|
-
if (typeof assistantReply ===
|
|
1352
|
+
if (typeof assistantReply === "string") {
|
|
1030
1353
|
const lastTurn = existing.turns.at(-1);
|
|
1031
1354
|
if (lastTurn && lastTurn.assistant !== assistantReply) {
|
|
1032
1355
|
lastTurn.assistant = assistantReply;
|
|
@@ -1040,15 +1363,17 @@ export class DelegatorMcpWorker {
|
|
|
1040
1363
|
jobId: job.jobId,
|
|
1041
1364
|
closed: false,
|
|
1042
1365
|
updatedAt: new Date().toISOString(),
|
|
1043
|
-
turns: [
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1366
|
+
turns: [
|
|
1367
|
+
{
|
|
1368
|
+
user: userPrompt,
|
|
1369
|
+
assistant: assistantReply,
|
|
1370
|
+
},
|
|
1371
|
+
],
|
|
1047
1372
|
});
|
|
1048
1373
|
}
|
|
1049
1374
|
|
|
1050
1375
|
async _runSyncJob(args, extra) {
|
|
1051
|
-
const job = this._createJob(args,
|
|
1376
|
+
const job = this._createJob(args, "sync");
|
|
1052
1377
|
this.jobs.set(job.jobId, job);
|
|
1053
1378
|
const result = await this._executeDirect(args, extra);
|
|
1054
1379
|
this._applyJobResult(job, result);
|
|
@@ -1065,7 +1390,9 @@ export async function runDelegatorMcpCli() {
|
|
|
1065
1390
|
try {
|
|
1066
1391
|
await worker.serveStdio();
|
|
1067
1392
|
} catch (error) {
|
|
1068
|
-
console.error(
|
|
1393
|
+
console.error(
|
|
1394
|
+
`[delegator-mcp] ${error instanceof Error ? error.message : String(error)}`,
|
|
1395
|
+
);
|
|
1069
1396
|
process.exitCode = 1;
|
|
1070
1397
|
}
|
|
1071
1398
|
}
|