triflux 10.3.4 → 10.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/bin/tfx-doctor-tui.mjs +1 -1
- package/bin/tfx-doctor.mjs +6 -1
- package/bin/tfx-profile.mjs +1 -1
- package/bin/tfx-setup-tui.mjs +1 -1
- package/bin/tfx-setup.mjs +6 -1
- package/bin/triflux.mjs +2396 -1140
- package/hooks/agent-route-guard.mjs +12 -8
- package/hooks/cross-review-tracker.mjs +21 -8
- package/hooks/error-context.mjs +19 -7
- package/hooks/hook-adaptive-collector.mjs +18 -16
- package/hooks/hook-manager.mjs +93 -32
- package/hooks/hook-orchestrator.mjs +108 -24
- package/hooks/hook-registry.json +11 -0
- package/hooks/keyword-rules.json +6 -10
- package/hooks/lib/resolve-root.mjs +1 -1
- package/hooks/mcp-config-watcher.mjs +6 -2
- package/hooks/pipeline-stop.mjs +3 -6
- package/hooks/safety-guard.mjs +99 -28
- package/hooks/session-start-fast.mjs +143 -0
- package/hooks/subagent-verifier.mjs +5 -4
- package/hub/account-broker.mjs +256 -60
- package/hub/adaptive-diagnostic.mjs +75 -48
- package/hub/adaptive-inject.mjs +95 -57
- package/hub/adaptive-memory.mjs +156 -42
- package/hub/adaptive.mjs +60 -31
- package/hub/assign-callbacks.mjs +67 -30
- package/hub/bridge.mjs +0 -1
- package/hub/cli-adapter-base.mjs +200 -48
- package/hub/codex-adapter.mjs +76 -96
- package/hub/codex-compat.mjs +3 -3
- package/hub/codex-preflight.mjs +63 -37
- package/hub/delegator/contracts.mjs +19 -23
- package/hub/delegator/index.mjs +3 -3
- package/hub/delegator/service.mjs +88 -64
- package/hub/delegator/tool-definitions.mjs +5 -5
- package/hub/fullcycle.mjs +33 -17
- package/hub/gemini-adapter.mjs +69 -94
- package/hub/hitl.mjs +89 -30
- package/hub/intent.mjs +161 -38
- package/hub/lib/cache-guard.mjs +43 -17
- package/hub/lib/mcp-response-cache.mjs +66 -32
- package/hub/lib/memory-store.mjs +285 -111
- package/hub/lib/path-utils.mjs +35 -37
- package/hub/lib/process-utils.mjs +106 -37
- package/hub/lib/spawn-trace.mjs +527 -0
- package/hub/lib/ssh-command.mjs +34 -4
- package/hub/lib/ssh-retry.mjs +5 -1
- package/hub/lib/uuidv7.mjs +4 -3
- package/hub/memory-doctor.mjs +266 -106
- package/hub/middleware/request-logger.mjs +61 -34
- package/hub/paths.mjs +9 -9
- package/hub/pipeline/gates/confidence.mjs +34 -15
- package/hub/pipeline/gates/consensus.mjs +27 -15
- package/hub/pipeline/gates/index.mjs +7 -3
- package/hub/pipeline/gates/selfcheck.mjs +57 -19
- package/hub/pipeline/index.mjs +77 -42
- package/hub/pipeline/state.mjs +10 -10
- package/hub/pipeline/transitions.mjs +40 -23
- package/hub/platform.mjs +57 -48
- package/hub/promote-penalties.mjs +25 -7
- package/hub/quality/deslop.mjs +70 -49
- package/hub/research.mjs +32 -25
- package/hub/router.mjs +240 -107
- package/hub/routing/complexity.mjs +132 -29
- package/hub/routing/index.mjs +17 -12
- package/hub/routing/q-learning.mjs +76 -28
- package/hub/server.mjs +4 -4
- package/hub/session-fingerprint.mjs +126 -60
- package/hub/state.mjs +84 -43
- package/hub/store-adapter.mjs +59 -26
- package/hub/store.mjs +356 -153
- package/hub/team/agent-map.json +22 -7
- package/hub/team/ansi.mjs +186 -122
- package/hub/team/backend.mjs +28 -10
- package/hub/team/cli/commands/attach.mjs +29 -9
- package/hub/team/cli/commands/control.mjs +29 -8
- package/hub/team/cli/commands/debug.mjs +32 -11
- package/hub/team/cli/commands/focus.mjs +38 -11
- 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 +11 -4
- package/hub/team/cli/commands/send.mjs +19 -6
- package/hub/team/cli/commands/start/index.mjs +154 -31
- package/hub/team/cli/commands/start/parse-args.mjs +38 -11
- package/hub/team/cli/commands/start/start-headless.mjs +112 -36
- package/hub/team/cli/commands/start/start-in-process.mjs +12 -2
- package/hub/team/cli/commands/start/start-mux.mjs +70 -21
- package/hub/team/cli/commands/start/start-wt.mjs +29 -12
- 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 +1 -1
- package/hub/team/cli/index.mjs +2 -2
- package/hub/team/cli/manifest.mjs +38 -8
- package/hub/team/cli/render.mjs +30 -8
- package/hub/team/cli/services/attach-fallback.mjs +31 -11
- package/hub/team/cli/services/hub-client.mjs +42 -14
- package/hub/team/cli/services/member-selector.mjs +11 -4
- package/hub/team/cli/services/native-control.mjs +48 -21
- package/hub/team/cli/services/runtime-mode.mjs +2 -1
- package/hub/team/cli/services/state-store.mjs +25 -8
- package/hub/team/cli/services/task-model.mjs +16 -6
- package/hub/team/conductor-mesh-bridge.mjs +24 -23
- package/hub/team/conductor.mjs +8 -4
- package/hub/team/dashboard-anchor.mjs +4 -5
- package/hub/team/dashboard-layout.mjs +3 -1
- package/hub/team/dashboard-open.mjs +41 -21
- package/hub/team/dashboard.mjs +76 -28
- package/hub/team/event-log.mjs +18 -10
- package/hub/team/handoff.mjs +31 -15
- package/hub/team/headless.mjs +2 -1
- package/hub/team/health-probe.mjs +69 -54
- package/hub/team/launcher-template.mjs +16 -13
- package/hub/team/native-supervisor.mjs +65 -21
- package/hub/team/native.mjs +74 -35
- package/hub/team/nativeProxy.mjs +184 -113
- package/hub/team/notify.mjs +119 -76
- package/hub/team/orchestrator.mjs +9 -4
- package/hub/team/pane.mjs +12 -7
- package/hub/team/process-cleanup.mjs +25 -16
- package/hub/team/psmux.mjs +491 -201
- package/hub/team/remote-probe.mjs +68 -52
- package/hub/team/remote-session.mjs +117 -59
- package/hub/team/remote-watcher.mjs +61 -33
- package/hub/team/routing.mjs +51 -25
- package/hub/team/runtime-strategy.mjs +3 -1
- package/hub/team/session.mjs +98 -34
- package/hub/team/staleState.mjs +72 -30
- package/hub/team/swarm-locks.mjs +15 -13
- package/hub/team/swarm-planner.mjs +32 -21
- package/hub/team/swarm-reconciler.mjs +48 -23
- package/hub/team/tui-lite.mjs +266 -68
- package/hub/team/tui-remote-adapter.mjs +14 -10
- package/hub/team/tui-viewer.mjs +99 -43
- package/hub/team/tui.mjs +708 -271
- package/hub/team/worktree-lifecycle.mjs +152 -58
- package/hub/team/wt-manager.mjs +24 -14
- package/hub/token-mode.mjs +71 -71
- package/hub/tray.mjs +66 -23
- package/hub/workers/claude-worker.mjs +162 -118
- package/hub/workers/codex-mcp.mjs +192 -141
- package/hub/workers/delegator-mcp.mjs +507 -333
- package/hub/workers/factory.mjs +8 -8
- package/hub/workers/gemini-worker.mjs +115 -84
- package/hub/workers/interface.mjs +6 -1
- package/hub/workers/worker-utils.mjs +21 -14
- package/hud/colors.mjs +27 -9
- package/hud/constants.mjs +162 -26
- package/hud/context-monitor.mjs +82 -41
- package/hud/hud-qos-status.mjs +129 -49
- package/hud/mission-board.mjs +6 -3
- package/hud/providers/claude.mjs +226 -115
- package/hud/providers/codex.mjs +62 -22
- package/hud/providers/gemini.mjs +168 -56
- package/hud/renderers.mjs +384 -119
- package/hud/terminal.mjs +101 -31
- package/hud/utils.mjs +78 -38
- package/mesh/index.mjs +11 -5
- package/mesh/mesh-budget.mjs +18 -9
- package/mesh/mesh-heartbeat.mjs +1 -1
- package/mesh/mesh-queue.mjs +3 -5
- package/mesh/mesh-router.mjs +5 -4
- package/package.json +2 -1
- package/scripts/__tests__/gen-skill-docs.test.mjs +36 -7
- package/scripts/__tests__/keyword-detector.test.mjs +77 -28
- package/scripts/__tests__/mcp-guard-engine.test.mjs +58 -20
- package/scripts/__tests__/remote-spawn-transfer.test.mjs +30 -19
- package/scripts/__tests__/remote-spawn.test.mjs +10 -4
- package/scripts/__tests__/session-start-fast.test.mjs +36 -0
- package/scripts/__tests__/skill-template.test.mjs +98 -50
- package/scripts/__tests__/smoke.test.mjs +1 -1
- package/scripts/__tests__/spawn-trace.test.mjs +102 -0
- package/scripts/__tests__/tfx-doctor-diagnose.test.mjs +48 -0
- package/scripts/cache-doctor.mjs +11 -4
- package/scripts/cache-warmup.mjs +96 -37
- package/scripts/claudemd-sync.mjs +27 -17
- package/scripts/codex-gateway-preflight.mjs +52 -37
- package/scripts/codex-mcp-gateway-sync.mjs +59 -39
- package/scripts/completions/tfx.bash +47 -47
- package/scripts/completions/tfx.fish +44 -44
- package/scripts/completions/tfx.zsh +83 -83
- package/scripts/config-audit.mjs +232 -0
- package/scripts/convert-to-tmpl.mjs +54 -0
- package/scripts/cross-review-gate.mjs +35 -12
- package/scripts/cross-review-tracker.mjs +21 -8
- package/scripts/demo.mjs +35 -17
- package/scripts/doctor-diagnose.mjs +284 -0
- package/scripts/gen-skill-docs.mjs +7 -2
- package/scripts/gen-skill-manifest.mjs +2 -1
- package/scripts/headless-guard.mjs +86 -48
- package/scripts/hub-ensure.mjs +45 -26
- package/scripts/keyword-detector.mjs +41 -20
- package/scripts/keyword-rules-expander.mjs +47 -30
- package/scripts/lib/claudemd-scanner.mjs +6 -1
- package/scripts/lib/context.mjs +3 -3
- package/scripts/lib/cross-review-utils.mjs +6 -3
- package/scripts/lib/env-probe.mjs +47 -28
- package/scripts/lib/gemini-profiles.mjs +44 -10
- package/scripts/lib/handoff.mjs +33 -17
- package/scripts/lib/hook-utils.mjs +8 -6
- package/scripts/lib/keyword-rules.mjs +43 -19
- package/scripts/lib/logger.mjs +24 -24
- package/scripts/lib/mcp-filter.mjs +377 -239
- package/scripts/lib/mcp-guard-engine.mjs +194 -79
- package/scripts/lib/mcp-manifest.mjs +23 -13
- package/scripts/lib/mcp-server-catalog.mjs +300 -63
- package/scripts/lib/psmux-info.mjs +11 -6
- package/scripts/lib/remote-spawn-transfer.mjs +44 -14
- package/scripts/lib/skill-template.mjs +30 -7
- package/scripts/mcp-check.mjs +58 -39
- package/scripts/mcp-gateway-config.mjs +83 -39
- package/scripts/mcp-gateway-ensure.mjs +43 -35
- package/scripts/mcp-gateway-integration-test.mjs +70 -58
- package/scripts/mcp-gateway-start.mjs +126 -60
- package/scripts/mcp-gateway-verify.mjs +24 -22
- package/scripts/mcp-safety-guard.mjs +44 -11
- package/scripts/notion-read.mjs +199 -84
- package/scripts/pack.mjs +94 -89
- package/scripts/preflight-cache.mjs +27 -10
- package/scripts/preinstall.mjs +42 -13
- package/scripts/remote-spawn.mjs +309 -94
- package/scripts/run.cjs +8 -5
- package/scripts/session-spawn-helper.mjs +130 -39
- package/scripts/session-stale-cleanup.mjs +123 -0
- package/scripts/setup.mjs +941 -492
- package/scripts/test-lock.mjs +20 -7
- package/scripts/test-tfx-route-no-claude-native.mjs +16 -12
- package/scripts/tfx-batch-stats.mjs +32 -11
- package/scripts/tfx-gate-activate.mjs +11 -4
- package/scripts/tfx-route-post.mjs +87 -20
- package/scripts/tfx-route-worker.mjs +57 -51
- package/scripts/tfx-route.sh +41 -124
- package/scripts/tmp-cleanup.mjs +21 -7
- package/scripts/token-snapshot.mjs +204 -85
- package/skills/.omc/state/agent-replay-8f0e10a9-9693-4410-96f5-a6b07e8ed995.jsonl +1 -0
- package/skills/.omc/state/idle-notif-cooldown.json +3 -0
- package/skills/.omc/state/last-tool-error.json +7 -0
- package/skills/.omc/state/subagent-tracking.json +7 -0
- package/skills/_templates/base.md +1 -6
- package/skills/merge-worktree/SKILL.md.tmpl +144 -0
- package/skills/shared/telemetry-segment.md +6 -0
- package/skills/star-prompt/SKILL.md.tmpl +222 -0
- package/skills/tfx-analysis/SKILL.md.tmpl +107 -0
- package/skills/tfx-analysis/skill.json +1 -6
- package/skills/tfx-auto/SKILL.md +1 -0
- package/skills/tfx-auto-codex/SKILL.md.tmpl +106 -0
- package/skills/tfx-auto-codex/skill.json +1 -3
- package/skills/tfx-autopilot/SKILL.md.tmpl +116 -0
- package/skills/tfx-autopilot/skill.json +1 -5
- package/skills/tfx-autoresearch/SKILL.md.tmpl +136 -0
- package/skills/tfx-autoroute/SKILL.md.tmpl +189 -0
- package/skills/tfx-autoroute/skill.json +1 -7
- package/skills/tfx-codex/SKILL.md +1 -0
- package/skills/tfx-codex/skill.json +1 -3
- package/skills/tfx-codex-swarm/SKILL.md.tmpl +16 -0
- package/skills/tfx-codex-swarm/evals/evals.json +1 -1
- package/skills/tfx-codex-swarm/skill.json +1 -4
- package/skills/tfx-codex-swarm-workspace/iteration-1/benchmark.json +54 -12
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/grading.json +35 -7
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/grading.json +35 -7
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/grading.json +25 -5
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/grading.json +25 -5
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/grading.json +20 -4
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/grading.json +16 -4
- package/skills/tfx-consensus/SKILL.md.tmpl +146 -0
- package/skills/tfx-debate/SKILL.md.tmpl +192 -0
- package/skills/tfx-debate/skill.json +1 -7
- package/skills/tfx-deep-analysis/SKILL.md.tmpl +228 -0
- package/skills/tfx-deep-analysis/skill.json +1 -5
- package/skills/tfx-deep-interview/SKILL.md.tmpl +203 -0
- package/skills/tfx-deep-plan/SKILL.md.tmpl +282 -0
- package/skills/tfx-deep-qa/SKILL.md.tmpl +165 -0
- package/skills/tfx-deep-qa/skill.json +1 -6
- package/skills/tfx-deep-research/SKILL.md.tmpl +217 -0
- package/skills/tfx-deep-review/SKILL.md.tmpl +179 -0
- package/skills/tfx-doctor/SKILL.md +21 -0
- package/skills/tfx-doctor/SKILL.md.tmpl +172 -0
- package/skills/tfx-doctor/skill.json +1 -3
- package/skills/tfx-find/SKILL.md +1 -0
- package/skills/tfx-forge/SKILL.md.tmpl +187 -0
- package/skills/tfx-fullcycle/SKILL.md.tmpl +286 -0
- package/skills/tfx-fullcycle/skill.json +1 -6
- package/skills/tfx-gemini/SKILL.md.tmpl +91 -0
- package/skills/tfx-gemini/skill.json +1 -3
- package/skills/tfx-hooks/SKILL.md.tmpl +216 -0
- package/skills/tfx-hooks/skill.json +1 -3
- package/skills/tfx-hub/SKILL.md.tmpl +212 -0
- package/skills/tfx-hub/skill.json +1 -3
- package/skills/tfx-index/SKILL.md +1 -0
- package/skills/tfx-index/skill.json +1 -6
- package/skills/tfx-interview/SKILL.md.tmpl +285 -0
- package/skills/tfx-multi/SKILL.md.tmpl +183 -0
- package/skills/tfx-multi/skill.json +1 -3
- package/skills/tfx-panel/SKILL.md.tmpl +189 -0
- package/skills/tfx-panel/skill.json +1 -7
- package/skills/tfx-persist/SKILL.md.tmpl +270 -0
- package/skills/tfx-persist/skill.json +1 -7
- package/skills/tfx-plan/SKILL.md +1 -0
- package/skills/tfx-plan/skill.json +1 -6
- package/skills/tfx-profile/SKILL.md.tmpl +239 -0
- package/skills/tfx-profile/skill.json +1 -3
- package/skills/tfx-prune/SKILL.md.tmpl +200 -0
- package/skills/tfx-prune/skill.json +1 -7
- package/skills/tfx-psmux-rules/SKILL.md.tmpl +326 -0
- package/skills/tfx-psmux-rules/skill.json +1 -4
- package/skills/tfx-qa/SKILL.md +1 -0
- package/skills/tfx-qa/skill.json +1 -6
- package/skills/tfx-ralph/SKILL.md.tmpl +28 -0
- package/skills/tfx-ralph/skill.json +1 -4
- package/skills/tfx-remote-setup/SKILL.md.tmpl +576 -0
- package/skills/tfx-remote-setup/skill.json +1 -3
- package/skills/tfx-remote-spawn/SKILL.md.tmpl +263 -0
- package/skills/tfx-remote-spawn/references/hosts.json +16 -0
- package/skills/tfx-remote-spawn/skill.json +1 -4
- package/skills/tfx-research/SKILL.md +1 -0
- package/skills/tfx-review/SKILL.md +1 -0
- package/skills/tfx-review/skill.json +1 -6
- package/skills/tfx-setup/SKILL.md.tmpl +504 -0
- package/skills/tfx-setup/skill.json +1 -3
- package/skills/tfx-swarm/SKILL.md +22 -0
- package/skills/tfx-swarm/SKILL.md.tmpl +218 -0
- package/tui/codex-profile.mjs +88 -33
- package/tui/core.mjs +45 -15
- package/tui/doctor.mjs +75 -28
- package/tui/gemini-profile.mjs +74 -29
- package/tui/monitor-data.mjs +8 -4
- package/tui/monitor.mjs +71 -27
- package/tui/setup.mjs +133 -42
|
@@ -1,30 +1,32 @@
|
|
|
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
|
|
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
10
|
|
|
11
|
-
import { McpServer } from
|
|
12
|
-
import { StdioServerTransport } from
|
|
13
|
-
import * as z from
|
|
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
14
|
|
|
15
|
-
import { CodexMcpWorker } from
|
|
16
|
-
import { GeminiWorker } from
|
|
15
|
+
import { CodexMcpWorker } from "./codex-mcp.mjs";
|
|
16
|
+
import { GeminiWorker } from "./gemini-worker.mjs";
|
|
17
17
|
|
|
18
18
|
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
|
|
19
19
|
|
|
20
20
|
// mcp-filter.mjs 동적 해석 — 프로젝트(hub/workers/)와 배포(scripts/hub/workers/) 양쪽 대응
|
|
21
21
|
const MCP_FILTER_CANDIDATES = [
|
|
22
|
-
resolve(SCRIPT_DIR,
|
|
23
|
-
resolve(SCRIPT_DIR,
|
|
22
|
+
resolve(SCRIPT_DIR, "../../scripts/lib/mcp-filter.mjs"), // 프로젝트 원본
|
|
23
|
+
resolve(SCRIPT_DIR, "../../lib/mcp-filter.mjs"), // 배포 (~/.claude/scripts/)
|
|
24
24
|
];
|
|
25
25
|
const mcpFilterPath = MCP_FILTER_CANDIDATES.find((p) => existsSync(p));
|
|
26
26
|
if (!mcpFilterPath) {
|
|
27
|
-
throw new Error(
|
|
27
|
+
throw new Error(
|
|
28
|
+
`mcp-filter.mjs not found. candidates: ${MCP_FILTER_CANDIDATES.join(", ")}`,
|
|
29
|
+
);
|
|
28
30
|
}
|
|
29
31
|
const {
|
|
30
32
|
buildPromptHint,
|
|
@@ -32,74 +34,77 @@ const {
|
|
|
32
34
|
getGeminiAllowedServers,
|
|
33
35
|
SUPPORTED_MCP_PROFILES,
|
|
34
36
|
} = await import(pathToFileURL(mcpFilterPath).href);
|
|
35
|
-
const SERVER_INFO = { name:
|
|
37
|
+
const SERVER_INFO = { name: "triflux-delegator", version: "1.0.0" };
|
|
36
38
|
const DEFAULT_CONTEXT_BYTES = 32 * 1024;
|
|
37
39
|
const DEFAULT_ROUTE_TIMEOUT_SEC = 120;
|
|
38
40
|
const DIRECT_PROGRESS_START = 5;
|
|
39
41
|
const DIRECT_PROGRESS_RUNNING = 60;
|
|
40
42
|
const DIRECT_PROGRESS_DONE = 100;
|
|
41
|
-
const SEARCH_ENGINE_CACHE_PATH = [
|
|
43
|
+
const SEARCH_ENGINE_CACHE_PATH = [".omc", "state", "search-engines.json"];
|
|
42
44
|
|
|
43
45
|
const AGENT_TIMEOUT_SEC = Object.freeze({
|
|
44
46
|
executor: 1080,
|
|
45
|
-
|
|
47
|
+
"build-fixer": 540,
|
|
46
48
|
debugger: 900,
|
|
47
|
-
|
|
49
|
+
"deep-executor": 3600,
|
|
48
50
|
architect: 3600,
|
|
49
51
|
planner: 3600,
|
|
50
52
|
critic: 3600,
|
|
51
53
|
analyst: 3600,
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
"code-reviewer": 1800,
|
|
55
|
+
"security-reviewer": 1800,
|
|
56
|
+
"quality-reviewer": 1800,
|
|
55
57
|
scientist: 1440,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
+
"scientist-deep": 3600,
|
|
59
|
+
"document-specialist": 1440,
|
|
58
60
|
designer: 900,
|
|
59
61
|
writer: 900,
|
|
60
62
|
explore: 300,
|
|
61
63
|
verifier: 1200,
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
"test-engineer": 300,
|
|
65
|
+
"qa-tester": 300,
|
|
64
66
|
spark: 180,
|
|
65
67
|
});
|
|
66
68
|
|
|
67
69
|
const CODEX_PROFILE_BY_AGENT = Object.freeze({
|
|
68
|
-
executor:
|
|
69
|
-
|
|
70
|
-
debugger:
|
|
71
|
-
|
|
72
|
-
architect:
|
|
73
|
-
planner:
|
|
74
|
-
critic:
|
|
75
|
-
analyst:
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
scientist:
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
verifier:
|
|
83
|
-
designer:
|
|
84
|
-
writer:
|
|
85
|
-
spark:
|
|
70
|
+
executor: "codex53_high",
|
|
71
|
+
"build-fixer": "codex53_low",
|
|
72
|
+
debugger: "codex53_high",
|
|
73
|
+
"deep-executor": "gpt54_xhigh",
|
|
74
|
+
architect: "gpt54_xhigh",
|
|
75
|
+
planner: "gpt54_xhigh",
|
|
76
|
+
critic: "gpt54_xhigh",
|
|
77
|
+
analyst: "gpt54_xhigh",
|
|
78
|
+
"code-reviewer": "codex53_high",
|
|
79
|
+
"security-reviewer": "codex53_high",
|
|
80
|
+
"quality-reviewer": "codex53_high",
|
|
81
|
+
scientist: "codex53_high",
|
|
82
|
+
"scientist-deep": "gpt54_high",
|
|
83
|
+
"document-specialist": "codex53_high",
|
|
84
|
+
verifier: "codex53_high",
|
|
85
|
+
designer: "gpt54_xhigh", // Gemini primary, codex fallback — UI/UX는 5.4 에이전틱
|
|
86
|
+
writer: "codex53_high", // Gemini primary, codex fallback용
|
|
87
|
+
spark: "spark53_low",
|
|
86
88
|
});
|
|
87
89
|
|
|
88
90
|
const GEMINI_MODEL_BY_AGENT = Object.freeze({
|
|
89
|
-
|
|
90
|
-
writer:
|
|
91
|
-
spark:
|
|
91
|
+
"build-fixer": "gemini-3-flash-preview",
|
|
92
|
+
writer: "gemini-3-flash-preview",
|
|
93
|
+
spark: "gemini-3-flash-preview",
|
|
92
94
|
});
|
|
93
95
|
|
|
94
96
|
const REVIEW_INSTRUCTION_BY_AGENT = Object.freeze({
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
97
|
+
"code-reviewer":
|
|
98
|
+
"코드 리뷰 모드로 동작하라. 버그, 리스크, 회귀, 테스트 누락을 우선 식별하라.",
|
|
99
|
+
"security-reviewer":
|
|
100
|
+
"보안 리뷰 모드로 동작하라. 취약점, 권한 경계, 비밀정보 노출 가능성을 우선 식별하라.",
|
|
101
|
+
"quality-reviewer":
|
|
102
|
+
"품질 리뷰 모드로 동작하라. 로직 결함, 유지보수성 저하, 테스트 누락을 우선 식별하라.",
|
|
98
103
|
});
|
|
99
104
|
|
|
100
105
|
function cloneEnv(env = process.env) {
|
|
101
106
|
return Object.fromEntries(
|
|
102
|
-
Object.entries(env).filter(([, value]) => typeof value ===
|
|
107
|
+
Object.entries(env).filter(([, value]) => typeof value === "string"),
|
|
103
108
|
);
|
|
104
109
|
}
|
|
105
110
|
|
|
@@ -108,7 +113,7 @@ function parseJsonArray(raw, fallback = []) {
|
|
|
108
113
|
try {
|
|
109
114
|
const parsed = JSON.parse(raw);
|
|
110
115
|
return Array.isArray(parsed)
|
|
111
|
-
? parsed.map((item) => String(item ??
|
|
116
|
+
? parsed.map((item) => String(item ?? "")).filter(Boolean)
|
|
112
117
|
: [...fallback];
|
|
113
118
|
} catch {
|
|
114
119
|
return [...fallback];
|
|
@@ -117,7 +122,9 @@ function parseJsonArray(raw, fallback = []) {
|
|
|
117
122
|
|
|
118
123
|
function resolveCandidatePath(candidate, cwd = process.cwd()) {
|
|
119
124
|
if (!candidate) return null;
|
|
120
|
-
const normalized = isAbsolute(candidate)
|
|
125
|
+
const normalized = isAbsolute(candidate)
|
|
126
|
+
? candidate
|
|
127
|
+
: resolve(cwd, candidate);
|
|
121
128
|
return existsSync(normalized) ? normalized : null;
|
|
122
129
|
}
|
|
123
130
|
|
|
@@ -126,8 +133,8 @@ function resolveRouteScript(explicitPath, cwd = process.cwd()) {
|
|
|
126
133
|
explicitPath,
|
|
127
134
|
process.env.TFX_DELEGATOR_ROUTE_SCRIPT,
|
|
128
135
|
process.env.TFX_ROUTE_SCRIPT,
|
|
129
|
-
resolve(SCRIPT_DIR,
|
|
130
|
-
resolve(cwd,
|
|
136
|
+
resolve(SCRIPT_DIR, "..", "..", "scripts", "tfx-route.sh"),
|
|
137
|
+
resolve(cwd, "scripts", "tfx-route.sh"),
|
|
131
138
|
];
|
|
132
139
|
|
|
133
140
|
for (const candidate of candidates) {
|
|
@@ -139,11 +146,11 @@ function resolveRouteScript(explicitPath, cwd = process.cwd()) {
|
|
|
139
146
|
}
|
|
140
147
|
|
|
141
148
|
function resolveCodexProfile(agentType) {
|
|
142
|
-
return CODEX_PROFILE_BY_AGENT[agentType] ||
|
|
149
|
+
return CODEX_PROFILE_BY_AGENT[agentType] || "high";
|
|
143
150
|
}
|
|
144
151
|
|
|
145
152
|
function resolveGeminiModel(agentType) {
|
|
146
|
-
return GEMINI_MODEL_BY_AGENT[agentType] ||
|
|
153
|
+
return GEMINI_MODEL_BY_AGENT[agentType] || "gemini-3.1-pro-preview";
|
|
147
154
|
}
|
|
148
155
|
|
|
149
156
|
function resolveTimeoutMs(agentType, timeoutMs) {
|
|
@@ -161,13 +168,13 @@ function resolveTimeoutSec(agentType, timeoutMs) {
|
|
|
161
168
|
}
|
|
162
169
|
|
|
163
170
|
function loadContextFromFile(contextFile) {
|
|
164
|
-
if (!contextFile) return
|
|
171
|
+
if (!contextFile) return "";
|
|
165
172
|
const resolved = resolveCandidatePath(contextFile);
|
|
166
|
-
if (!resolved) return
|
|
173
|
+
if (!resolved) return "";
|
|
167
174
|
try {
|
|
168
|
-
return readFileSync(resolved,
|
|
175
|
+
return readFileSync(resolved, "utf8").slice(0, DEFAULT_CONTEXT_BYTES);
|
|
169
176
|
} catch {
|
|
170
|
-
return
|
|
177
|
+
return "";
|
|
171
178
|
}
|
|
172
179
|
}
|
|
173
180
|
|
|
@@ -183,7 +190,9 @@ function withPromptHint(prompt, args) {
|
|
|
183
190
|
agentType: args.agentType,
|
|
184
191
|
requestedProfile: args.mcpProfile,
|
|
185
192
|
searchTool: args.searchTool,
|
|
186
|
-
workerIndex: Number.isInteger(args.workerIndex)
|
|
193
|
+
workerIndex: Number.isInteger(args.workerIndex)
|
|
194
|
+
? args.workerIndex
|
|
195
|
+
: undefined,
|
|
187
196
|
taskText: promptWithContext,
|
|
188
197
|
});
|
|
189
198
|
if (!hint) return promptWithContext;
|
|
@@ -192,8 +201,8 @@ function withPromptHint(prompt, args) {
|
|
|
192
201
|
|
|
193
202
|
function joinInstructions(...values) {
|
|
194
203
|
return values
|
|
195
|
-
.filter((value) => typeof value ===
|
|
196
|
-
.join(
|
|
204
|
+
.filter((value) => typeof value === "string" && value.trim())
|
|
205
|
+
.join("\n")
|
|
197
206
|
.trim();
|
|
198
207
|
}
|
|
199
208
|
|
|
@@ -202,36 +211,40 @@ function _loadAvailableServersFromSearchEngineCache(cwd = process.cwd()) {
|
|
|
202
211
|
if (!existsSync(cacheFile)) return null;
|
|
203
212
|
|
|
204
213
|
try {
|
|
205
|
-
const parsed = JSON.parse(readFileSync(cacheFile,
|
|
214
|
+
const parsed = JSON.parse(readFileSync(cacheFile, "utf8"));
|
|
206
215
|
if (!Array.isArray(parsed?.engines)) return null;
|
|
207
216
|
return parsed.engines
|
|
208
|
-
.filter((engine) => engine?.status ===
|
|
209
|
-
.map((engine) =>
|
|
217
|
+
.filter((engine) => engine?.status === "available")
|
|
218
|
+
.map((engine) =>
|
|
219
|
+
typeof engine?.name === "string" ? engine.name.trim() : "",
|
|
220
|
+
)
|
|
210
221
|
.filter(Boolean);
|
|
211
222
|
} catch {
|
|
212
223
|
return null;
|
|
213
224
|
}
|
|
214
225
|
}
|
|
215
226
|
|
|
216
|
-
function parseRouteType(stderr =
|
|
227
|
+
function parseRouteType(stderr = "") {
|
|
217
228
|
const match = stderr.match(/type=([a-z-]+)/);
|
|
218
229
|
if (!match) return null;
|
|
219
|
-
if (match[1] ===
|
|
220
|
-
if (match[1] ===
|
|
230
|
+
if (match[1] === "codex") return "codex";
|
|
231
|
+
if (match[1] === "gemini") return "gemini";
|
|
221
232
|
return match[1];
|
|
222
233
|
}
|
|
223
234
|
|
|
224
235
|
function summarizePayload(payload) {
|
|
225
|
-
if (typeof payload.output ===
|
|
226
|
-
|
|
236
|
+
if (typeof payload.output === "string" && payload.output.trim())
|
|
237
|
+
return payload.output.trim();
|
|
238
|
+
if (payload.mode === "async" && payload.job_id)
|
|
239
|
+
return `비동기 위임이 시작되었습니다. jobId=${payload.job_id}`;
|
|
227
240
|
if (payload.job_id) return `jobId=${payload.job_id} 상태=${payload.status}`;
|
|
228
241
|
if (payload.status) return `상태=${payload.status}`;
|
|
229
|
-
return payload.ok ?
|
|
242
|
+
return payload.ok ? "완료되었습니다." : "실패했습니다.";
|
|
230
243
|
}
|
|
231
244
|
|
|
232
245
|
function createToolResponse(payload, { isError = false } = {}) {
|
|
233
246
|
return {
|
|
234
|
-
content: [{ type:
|
|
247
|
+
content: [{ type: "text", text: summarizePayload(payload) }],
|
|
235
248
|
structuredContent: payload,
|
|
236
249
|
isError,
|
|
237
250
|
};
|
|
@@ -240,55 +253,95 @@ function createToolResponse(payload, { isError = false } = {}) {
|
|
|
240
253
|
function createErrorPayload(message, extras = {}) {
|
|
241
254
|
return {
|
|
242
255
|
ok: false,
|
|
243
|
-
status:
|
|
256
|
+
status: "failed",
|
|
244
257
|
error: message,
|
|
245
258
|
...extras,
|
|
246
259
|
};
|
|
247
260
|
}
|
|
248
261
|
|
|
249
262
|
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
|
-
|
|
263
|
+
prompt: z.string().min(1).describe("위임할 프롬프트"),
|
|
264
|
+
provider: z
|
|
265
|
+
.enum(["auto", "codex", "gemini"])
|
|
266
|
+
.default("auto")
|
|
267
|
+
.describe("사용할 provider"),
|
|
268
|
+
mode: z
|
|
269
|
+
.enum(["sync", "async"])
|
|
270
|
+
.default("sync")
|
|
271
|
+
.describe("동기 또는 비동기 실행"),
|
|
272
|
+
agentType: z
|
|
273
|
+
.string()
|
|
274
|
+
.default("executor")
|
|
275
|
+
.describe("tfx-route 역할명 또는 direct 실행 역할"),
|
|
276
|
+
cwd: z.string().optional().describe("작업 디렉터리"),
|
|
277
|
+
timeoutMs: z
|
|
278
|
+
.number()
|
|
279
|
+
.int()
|
|
280
|
+
.positive()
|
|
281
|
+
.optional()
|
|
282
|
+
.describe("요청 타임아웃(ms)"),
|
|
283
|
+
sessionKey: z.string().optional().describe("Codex warm session 재사용 키"),
|
|
284
|
+
resetSession: z.boolean().optional().describe("기존 Codex 세션 초기화 여부"),
|
|
285
|
+
mcpProfile: z.enum(SUPPORTED_MCP_PROFILES).default("auto"),
|
|
286
|
+
contextFile: z
|
|
287
|
+
.string()
|
|
288
|
+
.optional()
|
|
289
|
+
.describe("tfx-route prior_context 파일 경로"),
|
|
290
|
+
availableServers: z
|
|
291
|
+
.array(z.string())
|
|
292
|
+
.optional()
|
|
293
|
+
.describe("Codex에 등록된 MCP 서버 이름 목록 (config override 대상)"),
|
|
294
|
+
searchTool: z
|
|
295
|
+
.enum(["brave-search", "tavily", "exa"])
|
|
296
|
+
.optional()
|
|
297
|
+
.describe("검색 우선 도구"),
|
|
298
|
+
workerIndex: z
|
|
299
|
+
.number()
|
|
300
|
+
.int()
|
|
301
|
+
.positive()
|
|
302
|
+
.optional()
|
|
303
|
+
.describe("병렬 워커 인덱스"),
|
|
304
|
+
model: z.string().optional().describe("직접 실행 시 모델 오버라이드"),
|
|
305
|
+
developerInstructions: z
|
|
306
|
+
.string()
|
|
307
|
+
.optional()
|
|
308
|
+
.describe("직접 실행 시 추가 개발자 지침"),
|
|
309
|
+
compactPrompt: z.string().optional().describe("Codex compact prompt"),
|
|
310
|
+
threadId: z.string().optional().describe("Codex 직접 실행 시 기존 threadId"),
|
|
311
|
+
codexTransport: z
|
|
312
|
+
.enum(["auto", "mcp", "exec"])
|
|
313
|
+
.optional()
|
|
314
|
+
.describe("route 경로용 Codex transport"),
|
|
315
|
+
noClaudeNative: z
|
|
316
|
+
.boolean()
|
|
317
|
+
.optional()
|
|
318
|
+
.describe("route 경로용 TFX_NO_CLAUDE_NATIVE"),
|
|
319
|
+
teamName: z.string().optional().describe("TFX_TEAM_NAME"),
|
|
320
|
+
teamTaskId: z.string().optional().describe("TFX_TEAM_TASK_ID"),
|
|
321
|
+
teamAgentName: z.string().optional().describe("TFX_TEAM_AGENT_NAME"),
|
|
322
|
+
teamLeadName: z.string().optional().describe("TFX_TEAM_LEAD_NAME"),
|
|
323
|
+
hubUrl: z.string().optional().describe("TFX_HUB_URL"),
|
|
274
324
|
});
|
|
275
325
|
|
|
276
326
|
const DelegateStatusInputSchema = z.object({
|
|
277
|
-
jobId: z.string().min(1).describe(
|
|
327
|
+
jobId: z.string().min(1).describe("조회할 비동기 job ID"),
|
|
278
328
|
});
|
|
279
329
|
|
|
280
330
|
const DelegateReplyInputSchema = z.object({
|
|
281
|
-
job_id: z.string().min(1).describe(
|
|
282
|
-
reply: z.string().min(1).describe(
|
|
283
|
-
done: z
|
|
331
|
+
job_id: z.string().min(1).describe("후속 응답을 보낼 기존 delegate job ID"),
|
|
332
|
+
reply: z.string().min(1).describe("후속 사용자 응답"),
|
|
333
|
+
done: z
|
|
334
|
+
.boolean()
|
|
335
|
+
.default(false)
|
|
336
|
+
.describe("true이면 응답 처리 후 대화를 종료"),
|
|
284
337
|
});
|
|
285
338
|
|
|
286
339
|
const DelegateOutputSchema = z.object({
|
|
287
340
|
ok: z.boolean(),
|
|
288
341
|
jobId: z.string().optional(),
|
|
289
342
|
job_id: z.string().optional(),
|
|
290
|
-
mode: z.enum([
|
|
291
|
-
status: z.enum([
|
|
343
|
+
mode: z.enum(["sync", "async"]).optional(),
|
|
344
|
+
status: z.enum(["running", "completed", "failed"]).optional(),
|
|
292
345
|
error: z.string().optional(),
|
|
293
346
|
providerRequested: z.string().optional(),
|
|
294
347
|
providerResolved: z.string().nullable().optional(),
|
|
@@ -308,29 +361,33 @@ const DelegateOutputSchema = z.object({
|
|
|
308
361
|
|
|
309
362
|
function isTeamRouteRequested(args) {
|
|
310
363
|
return Boolean(
|
|
311
|
-
args.teamName
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
364
|
+
args.teamName ||
|
|
365
|
+
args.teamTaskId ||
|
|
366
|
+
args.teamAgentName ||
|
|
367
|
+
args.teamLeadName ||
|
|
368
|
+
args.hubUrl,
|
|
316
369
|
);
|
|
317
370
|
}
|
|
318
371
|
|
|
319
372
|
function pickRouteMode(provider) {
|
|
320
|
-
return provider ===
|
|
373
|
+
return provider === "auto" ? "auto" : provider;
|
|
321
374
|
}
|
|
322
375
|
|
|
323
376
|
function sanitizeDelegateArgs(args = {}) {
|
|
324
377
|
return {
|
|
325
|
-
provider: args.provider ||
|
|
326
|
-
agentType: args.agentType ||
|
|
378
|
+
provider: args.provider || "auto",
|
|
379
|
+
agentType: args.agentType || "executor",
|
|
327
380
|
cwd: args.cwd || null,
|
|
328
|
-
timeoutMs: Number.isFinite(Number(args.timeoutMs))
|
|
381
|
+
timeoutMs: Number.isFinite(Number(args.timeoutMs))
|
|
382
|
+
? Math.trunc(Number(args.timeoutMs))
|
|
383
|
+
: null,
|
|
329
384
|
sessionKey: args.sessionKey || null,
|
|
330
385
|
resetSession: Boolean(args.resetSession),
|
|
331
|
-
mcpProfile: args.mcpProfile ||
|
|
386
|
+
mcpProfile: args.mcpProfile || "auto",
|
|
332
387
|
contextFile: args.contextFile || null,
|
|
333
|
-
availableServers: Array.isArray(args.availableServers)
|
|
388
|
+
availableServers: Array.isArray(args.availableServers)
|
|
389
|
+
? args.availableServers
|
|
390
|
+
: null,
|
|
334
391
|
searchTool: args.searchTool || null,
|
|
335
392
|
workerIndex: Number.isInteger(args.workerIndex) ? args.workerIndex : null,
|
|
336
393
|
model: args.model || null,
|
|
@@ -348,21 +405,21 @@ function sanitizeDelegateArgs(args = {}) {
|
|
|
348
405
|
}
|
|
349
406
|
|
|
350
407
|
function formatConversationTranscript(turns = []) {
|
|
351
|
-
return turns
|
|
352
|
-
|
|
353
|
-
`Turn ${index + 1} user:\n${turn.user}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
408
|
+
return turns
|
|
409
|
+
.map((turn, index) => {
|
|
410
|
+
const parts = [`Turn ${index + 1} user:\n${turn.user}`];
|
|
411
|
+
if (typeof turn.assistant === "string" && turn.assistant.trim()) {
|
|
412
|
+
parts.push(`Turn ${index + 1} assistant:\n${turn.assistant}`);
|
|
413
|
+
}
|
|
414
|
+
return parts.join("\n\n");
|
|
415
|
+
})
|
|
416
|
+
.join("\n\n");
|
|
360
417
|
}
|
|
361
418
|
|
|
362
419
|
async function emitProgress(extra, progress, total, message) {
|
|
363
420
|
if (extra?._meta?.progressToken === undefined) return;
|
|
364
421
|
await extra.sendNotification({
|
|
365
|
-
method:
|
|
422
|
+
method: "notifications/progress",
|
|
366
423
|
params: {
|
|
367
424
|
progressToken: extra._meta.progressToken,
|
|
368
425
|
progress,
|
|
@@ -373,34 +430,39 @@ async function emitProgress(extra, progress, total, message) {
|
|
|
373
430
|
}
|
|
374
431
|
|
|
375
432
|
export class DelegatorMcpWorker {
|
|
376
|
-
type =
|
|
433
|
+
type = "delegator";
|
|
377
434
|
|
|
378
435
|
constructor(options = {}) {
|
|
379
436
|
this.cwd = options.cwd || process.cwd();
|
|
380
437
|
this.env = cloneEnv({ ...cloneEnv(process.env), ...cloneEnv(options.env) });
|
|
381
438
|
this.routeScript = resolveRouteScript(options.routeScript, this.cwd);
|
|
382
|
-
this.bashCommand =
|
|
383
|
-
||
|
|
384
|
-
|
|
385
|
-
||
|
|
439
|
+
this.bashCommand =
|
|
440
|
+
options.bashCommand ||
|
|
441
|
+
this.env.TFX_DELEGATOR_BASH_COMMAND ||
|
|
442
|
+
this.env.BASH_BIN ||
|
|
443
|
+
"bash";
|
|
386
444
|
|
|
387
445
|
this.codexWorker = new CodexMcpWorker({
|
|
388
|
-
command:
|
|
389
|
-
||
|
|
390
|
-
|
|
391
|
-
||
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
446
|
+
command:
|
|
447
|
+
options.codexCommand ||
|
|
448
|
+
this.env.TFX_DELEGATOR_CODEX_COMMAND ||
|
|
449
|
+
this.env.CODEX_BIN ||
|
|
450
|
+
"codex",
|
|
451
|
+
args:
|
|
452
|
+
Array.isArray(options.codexArgs) && options.codexArgs.length
|
|
453
|
+
? options.codexArgs
|
|
454
|
+
: parseJsonArray(this.env.TFX_DELEGATOR_CODEX_ARGS_JSON, []),
|
|
395
455
|
cwd: this.cwd,
|
|
396
456
|
env: this.env,
|
|
397
457
|
clientInfo: { name: SERVER_INFO.name, version: SERVER_INFO.version },
|
|
398
458
|
});
|
|
399
459
|
|
|
400
|
-
this.geminiCommand =
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
460
|
+
this.geminiCommand =
|
|
461
|
+
options.geminiCommand || this.env.GEMINI_BIN || "gemini";
|
|
462
|
+
this.geminiCommandArgs =
|
|
463
|
+
Array.isArray(options.geminiArgs) && options.geminiArgs.length
|
|
464
|
+
? [...options.geminiArgs]
|
|
465
|
+
: parseJsonArray(this.env.GEMINI_BIN_ARGS_JSON, []);
|
|
404
466
|
|
|
405
467
|
this.server = null;
|
|
406
468
|
this.transport = null;
|
|
@@ -424,32 +486,48 @@ export class DelegatorMcpWorker {
|
|
|
424
486
|
capabilities: { logging: {} },
|
|
425
487
|
});
|
|
426
488
|
|
|
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
|
-
|
|
489
|
+
server.registerTool(
|
|
490
|
+
"triflux-delegate",
|
|
491
|
+
{
|
|
492
|
+
description:
|
|
493
|
+
"새 위임을 실행합니다. codex/gemini direct 경로와 tfx-route 기반 auto 라우팅을 모두 지원합니다.",
|
|
494
|
+
inputSchema: DelegateInputSchema,
|
|
495
|
+
outputSchema: DelegateOutputSchema,
|
|
496
|
+
},
|
|
497
|
+
async (args, extra) => {
|
|
498
|
+
const payload = await this.delegate(args, extra);
|
|
499
|
+
return createToolResponse(payload, {
|
|
500
|
+
isError: payload.ok === false && payload.mode !== "async",
|
|
501
|
+
});
|
|
502
|
+
},
|
|
503
|
+
);
|
|
504
|
+
|
|
505
|
+
server.registerTool(
|
|
506
|
+
"triflux-delegate-status",
|
|
507
|
+
{
|
|
508
|
+
description: "비동기 위임 job 상태를 조회합니다.",
|
|
509
|
+
inputSchema: DelegateStatusInputSchema,
|
|
510
|
+
outputSchema: DelegateOutputSchema,
|
|
511
|
+
},
|
|
512
|
+
async ({ jobId }, extra) => {
|
|
513
|
+
const payload = await this.getJobStatus(jobId, extra);
|
|
514
|
+
return createToolResponse(payload, { isError: payload.ok === false });
|
|
515
|
+
},
|
|
516
|
+
);
|
|
517
|
+
|
|
518
|
+
server.registerTool(
|
|
519
|
+
"triflux-delegate-reply",
|
|
520
|
+
{
|
|
521
|
+
description:
|
|
522
|
+
"기존 delegate job에 후속 응답을 보내고, Gemini direct job이면 multi-turn 대화를 이어갑니다.",
|
|
523
|
+
inputSchema: DelegateReplyInputSchema,
|
|
524
|
+
outputSchema: DelegateOutputSchema,
|
|
525
|
+
},
|
|
526
|
+
async (args, extra) => {
|
|
527
|
+
const payload = await this.reply(args, extra);
|
|
528
|
+
return createToolResponse(payload, { isError: payload.ok === false });
|
|
529
|
+
},
|
|
530
|
+
);
|
|
453
531
|
|
|
454
532
|
this.server = server;
|
|
455
533
|
this.ready = true;
|
|
@@ -467,7 +545,9 @@ export class DelegatorMcpWorker {
|
|
|
467
545
|
this.ready = false;
|
|
468
546
|
|
|
469
547
|
for (const child of this.routeChildren) {
|
|
470
|
-
try {
|
|
548
|
+
try {
|
|
549
|
+
child.kill();
|
|
550
|
+
} catch {}
|
|
471
551
|
}
|
|
472
552
|
this.routeChildren.clear();
|
|
473
553
|
|
|
@@ -496,7 +576,7 @@ export class DelegatorMcpWorker {
|
|
|
496
576
|
async execute(prompt, options = {}) {
|
|
497
577
|
const result = await this._executeDirect({ prompt, ...options });
|
|
498
578
|
return {
|
|
499
|
-
output: result.output || result.error ||
|
|
579
|
+
output: result.output || result.error || "",
|
|
500
580
|
exitCode: result.exitCode ?? (result.ok ? 0 : 1),
|
|
501
581
|
threadId: result.threadId || null,
|
|
502
582
|
sessionKey: result.sessionKey || null,
|
|
@@ -505,7 +585,7 @@ export class DelegatorMcpWorker {
|
|
|
505
585
|
}
|
|
506
586
|
|
|
507
587
|
async delegate(args, extra) {
|
|
508
|
-
if (args.mode ===
|
|
588
|
+
if (args.mode === "async") {
|
|
509
589
|
return this._startAsyncJob(args, extra);
|
|
510
590
|
}
|
|
511
591
|
return this._runSyncJob(args, extra);
|
|
@@ -518,11 +598,11 @@ export class DelegatorMcpWorker {
|
|
|
518
598
|
}
|
|
519
599
|
|
|
520
600
|
const payload = this._serializeJob(job);
|
|
521
|
-
if (job.status ===
|
|
601
|
+
if (job.status === "running") {
|
|
522
602
|
await emitProgress(extra, 25, 100, `job ${jobId} 실행 중`);
|
|
523
|
-
} else if (job.status ===
|
|
603
|
+
} else if (job.status === "completed") {
|
|
524
604
|
await emitProgress(extra, DIRECT_PROGRESS_DONE, 100, `job ${jobId} 완료`);
|
|
525
|
-
} else if (job.status ===
|
|
605
|
+
} else if (job.status === "failed") {
|
|
526
606
|
await emitProgress(extra, DIRECT_PROGRESS_DONE, 100, `job ${jobId} 실패`);
|
|
527
607
|
}
|
|
528
608
|
return payload;
|
|
@@ -531,28 +611,51 @@ export class DelegatorMcpWorker {
|
|
|
531
611
|
async reply({ job_id, reply, done = false }, extra) {
|
|
532
612
|
const job = this.jobs.get(job_id);
|
|
533
613
|
if (!job) {
|
|
534
|
-
return createErrorPayload(`알 수 없는 jobId: ${job_id}`, {
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
614
|
+
return createErrorPayload(`알 수 없는 jobId: ${job_id}`, {
|
|
615
|
+
jobId: job_id,
|
|
616
|
+
job_id,
|
|
617
|
+
});
|
|
538
618
|
}
|
|
539
|
-
if (job.
|
|
540
|
-
return createErrorPayload(
|
|
619
|
+
if (job.status === "running") {
|
|
620
|
+
return createErrorPayload(`job ${job_id}가 아직 실행 중입니다.`, {
|
|
541
621
|
jobId: job_id,
|
|
542
622
|
job_id,
|
|
543
623
|
});
|
|
544
624
|
}
|
|
625
|
+
if (
|
|
626
|
+
job.providerRequested !== "gemini" ||
|
|
627
|
+
job.transport !== "gemini-worker"
|
|
628
|
+
) {
|
|
629
|
+
return createErrorPayload(
|
|
630
|
+
"delegate-reply는 현재 direct Gemini job에만 지원됩니다.",
|
|
631
|
+
{
|
|
632
|
+
jobId: job_id,
|
|
633
|
+
job_id,
|
|
634
|
+
},
|
|
635
|
+
);
|
|
636
|
+
}
|
|
545
637
|
|
|
546
638
|
const conversation = this.geminiConversations.get(job_id);
|
|
547
639
|
if (!conversation) {
|
|
548
|
-
return createErrorPayload(`Gemini 대화 컨텍스트가 없습니다: ${job_id}`, {
|
|
640
|
+
return createErrorPayload(`Gemini 대화 컨텍스트가 없습니다: ${job_id}`, {
|
|
641
|
+
jobId: job_id,
|
|
642
|
+
job_id,
|
|
643
|
+
});
|
|
549
644
|
}
|
|
550
645
|
if (conversation.closed) {
|
|
551
|
-
return createErrorPayload(`이미 종료된 대화입니다: ${job_id}`, {
|
|
646
|
+
return createErrorPayload(`이미 종료된 대화입니다: ${job_id}`, {
|
|
647
|
+
jobId: job_id,
|
|
648
|
+
job_id,
|
|
649
|
+
});
|
|
552
650
|
}
|
|
553
651
|
|
|
554
|
-
await emitProgress(
|
|
555
|
-
|
|
652
|
+
await emitProgress(
|
|
653
|
+
extra,
|
|
654
|
+
DIRECT_PROGRESS_START,
|
|
655
|
+
100,
|
|
656
|
+
`job ${job_id} 후속 응답을 시작합니다.`,
|
|
657
|
+
);
|
|
658
|
+
job.status = "running";
|
|
556
659
|
job.updatedAt = new Date().toISOString();
|
|
557
660
|
|
|
558
661
|
const worker = this._createGeminiWorker();
|
|
@@ -564,8 +667,10 @@ export class DelegatorMcpWorker {
|
|
|
564
667
|
cwd: job.requestArgs.cwd || this.cwd,
|
|
565
668
|
timeoutMs: resolveTimeoutMs(job.agentType, job.requestArgs.timeoutMs),
|
|
566
669
|
model: job.requestArgs.model || resolveGeminiModel(job.agentType),
|
|
567
|
-
approvalMode:
|
|
568
|
-
allowedMcpServerNames: getGeminiAllowedServers(
|
|
670
|
+
approvalMode: "yolo",
|
|
671
|
+
allowedMcpServerNames: getGeminiAllowedServers(
|
|
672
|
+
this._getMcpPolicyOptions(job.requestArgs),
|
|
673
|
+
),
|
|
569
674
|
});
|
|
570
675
|
|
|
571
676
|
conversation.turns.push({
|
|
@@ -580,26 +685,34 @@ export class DelegatorMcpWorker {
|
|
|
580
685
|
|
|
581
686
|
this._applyJobResult(job, {
|
|
582
687
|
ok: result.exitCode === 0,
|
|
583
|
-
status: result.exitCode === 0 ?
|
|
584
|
-
providerRequested:
|
|
585
|
-
providerResolved:
|
|
688
|
+
status: result.exitCode === 0 ? "completed" : "failed",
|
|
689
|
+
providerRequested: "gemini",
|
|
690
|
+
providerResolved: "gemini",
|
|
586
691
|
agentType: job.agentType,
|
|
587
|
-
transport:
|
|
692
|
+
transport: "gemini-worker",
|
|
588
693
|
exitCode: result.exitCode,
|
|
589
694
|
output: result.output,
|
|
590
695
|
sessionKey: result.sessionKey || job.sessionKey || null,
|
|
591
696
|
});
|
|
592
|
-
await emitProgress(
|
|
697
|
+
await emitProgress(
|
|
698
|
+
extra,
|
|
699
|
+
DIRECT_PROGRESS_DONE,
|
|
700
|
+
100,
|
|
701
|
+
`job ${job_id} 후속 응답이 완료되었습니다.`,
|
|
702
|
+
);
|
|
593
703
|
return this._serializeJob(job);
|
|
594
704
|
} catch (error) {
|
|
595
705
|
const message = error instanceof Error ? error.message : String(error);
|
|
596
|
-
this._applyJobResult(
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
706
|
+
this._applyJobResult(
|
|
707
|
+
job,
|
|
708
|
+
createErrorPayload(message, {
|
|
709
|
+
mode: job.mode,
|
|
710
|
+
providerRequested: "gemini",
|
|
711
|
+
providerResolved: "gemini",
|
|
712
|
+
agentType: job.agentType,
|
|
713
|
+
transport: "gemini-worker",
|
|
714
|
+
}),
|
|
715
|
+
);
|
|
603
716
|
return this._serializeJob(job);
|
|
604
717
|
} finally {
|
|
605
718
|
await worker.stop().catch(() => {});
|
|
@@ -617,15 +730,17 @@ export class DelegatorMcpWorker {
|
|
|
617
730
|
}
|
|
618
731
|
|
|
619
732
|
_buildDirectPrompt(args) {
|
|
620
|
-
return withContext(String(args.prompt ??
|
|
733
|
+
return withContext(String(args.prompt ?? ""), args.contextFile);
|
|
621
734
|
}
|
|
622
735
|
|
|
623
736
|
_buildDirectPromptWithHint(args) {
|
|
624
|
-
return withPromptHint(String(args.prompt ??
|
|
625
|
-
agentType: args.agentType ||
|
|
626
|
-
mcpProfile: args.mcpProfile ||
|
|
737
|
+
return withPromptHint(String(args.prompt ?? ""), {
|
|
738
|
+
agentType: args.agentType || "executor",
|
|
739
|
+
mcpProfile: args.mcpProfile || "auto",
|
|
627
740
|
searchTool: args.searchTool,
|
|
628
|
-
workerIndex: Number.isInteger(args.workerIndex)
|
|
741
|
+
workerIndex: Number.isInteger(args.workerIndex)
|
|
742
|
+
? args.workerIndex
|
|
743
|
+
: undefined,
|
|
629
744
|
contextFile: args.contextFile,
|
|
630
745
|
});
|
|
631
746
|
}
|
|
@@ -633,26 +748,30 @@ export class DelegatorMcpWorker {
|
|
|
633
748
|
_buildGeminiReplyPrompt(conversation, reply) {
|
|
634
749
|
const transcript = formatConversationTranscript(conversation.turns);
|
|
635
750
|
return [
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
751
|
+
"Continue the conversation using the prior transcript below.",
|
|
752
|
+
"",
|
|
753
|
+
"<conversation_history>",
|
|
639
754
|
transcript,
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
755
|
+
"</conversation_history>",
|
|
756
|
+
"",
|
|
757
|
+
"<latest_user_reply>",
|
|
643
758
|
reply,
|
|
644
|
-
|
|
645
|
-
].join(
|
|
759
|
+
"</latest_user_reply>",
|
|
760
|
+
].join("\n");
|
|
646
761
|
}
|
|
647
762
|
|
|
648
763
|
_getMcpPolicyOptions(args) {
|
|
649
764
|
return {
|
|
650
|
-
agentType: args.agentType ||
|
|
651
|
-
requestedProfile: args.mcpProfile ||
|
|
765
|
+
agentType: args.agentType || "executor",
|
|
766
|
+
requestedProfile: args.mcpProfile || "auto",
|
|
652
767
|
searchTool: args.searchTool,
|
|
653
|
-
workerIndex: Number.isInteger(args.workerIndex)
|
|
654
|
-
|
|
655
|
-
|
|
768
|
+
workerIndex: Number.isInteger(args.workerIndex)
|
|
769
|
+
? args.workerIndex
|
|
770
|
+
: undefined,
|
|
771
|
+
taskText: withContext(String(args.prompt ?? ""), args.contextFile),
|
|
772
|
+
availableServers: Array.isArray(args.availableServers)
|
|
773
|
+
? args.availableServers
|
|
774
|
+
: undefined,
|
|
656
775
|
};
|
|
657
776
|
}
|
|
658
777
|
|
|
@@ -661,11 +780,16 @@ export class DelegatorMcpWorker {
|
|
|
661
780
|
}
|
|
662
781
|
|
|
663
782
|
_shouldUseRoute(args) {
|
|
664
|
-
return args.provider ===
|
|
783
|
+
return args.provider === "auto" || isTeamRouteRequested(args);
|
|
665
784
|
}
|
|
666
785
|
|
|
667
786
|
async _executeDirect(args, extra = null) {
|
|
668
|
-
await emitProgress(
|
|
787
|
+
await emitProgress(
|
|
788
|
+
extra,
|
|
789
|
+
DIRECT_PROGRESS_START,
|
|
790
|
+
100,
|
|
791
|
+
"위임 실행을 시작합니다.",
|
|
792
|
+
);
|
|
669
793
|
|
|
670
794
|
const runViaRoute = this._shouldUseRoute(args);
|
|
671
795
|
|
|
@@ -674,50 +798,63 @@ export class DelegatorMcpWorker {
|
|
|
674
798
|
? await this._executeRoute(args, extra)
|
|
675
799
|
: await this._executeWorker(args, extra);
|
|
676
800
|
|
|
677
|
-
await emitProgress(
|
|
801
|
+
await emitProgress(
|
|
802
|
+
extra,
|
|
803
|
+
DIRECT_PROGRESS_DONE,
|
|
804
|
+
100,
|
|
805
|
+
"위임이 완료되었습니다.",
|
|
806
|
+
);
|
|
678
807
|
return result;
|
|
679
808
|
} catch (error) {
|
|
680
809
|
const message = error instanceof Error ? error.message : String(error);
|
|
681
810
|
return createErrorPayload(message, {
|
|
682
|
-
mode:
|
|
811
|
+
mode: "sync",
|
|
683
812
|
providerRequested: args.provider,
|
|
684
813
|
agentType: args.agentType,
|
|
685
|
-
transport: runViaRoute ?
|
|
814
|
+
transport: runViaRoute ? "route-script" : `${args.provider}-worker`,
|
|
686
815
|
});
|
|
687
816
|
}
|
|
688
817
|
}
|
|
689
818
|
|
|
690
819
|
async _executeWorker(args, extra) {
|
|
691
|
-
await emitProgress(
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
args.
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
820
|
+
await emitProgress(
|
|
821
|
+
extra,
|
|
822
|
+
DIRECT_PROGRESS_RUNNING,
|
|
823
|
+
100,
|
|
824
|
+
"직접 워커 경로로 실행 중입니다.",
|
|
825
|
+
);
|
|
826
|
+
|
|
827
|
+
if (args.provider === "codex") {
|
|
828
|
+
const result = await this.codexWorker.execute(
|
|
829
|
+
this._buildDirectPrompt(args),
|
|
830
|
+
{
|
|
831
|
+
cwd: args.cwd || this.cwd,
|
|
832
|
+
timeoutMs: resolveTimeoutMs(args.agentType, args.timeoutMs),
|
|
833
|
+
sessionKey: args.sessionKey,
|
|
834
|
+
threadId: args.threadId,
|
|
835
|
+
resetSession: args.resetSession,
|
|
836
|
+
profile: resolveCodexProfile(args.agentType),
|
|
837
|
+
sandbox: "danger-full-access",
|
|
838
|
+
approvalPolicy: "never",
|
|
839
|
+
developerInstructions: joinInstructions(
|
|
840
|
+
REVIEW_INSTRUCTION_BY_AGENT[args.agentType],
|
|
841
|
+
this._buildPromptHintInstruction(args),
|
|
842
|
+
args.developerInstructions,
|
|
843
|
+
),
|
|
844
|
+
config: getCodexMcpConfig(this._getMcpPolicyOptions(args)),
|
|
845
|
+
compactPrompt: args.compactPrompt,
|
|
846
|
+
model: args.model,
|
|
847
|
+
},
|
|
848
|
+
);
|
|
712
849
|
|
|
713
850
|
return {
|
|
714
851
|
ok: result.exitCode === 0,
|
|
715
|
-
mode:
|
|
716
|
-
status: result.exitCode === 0 ?
|
|
717
|
-
providerRequested:
|
|
718
|
-
providerResolved:
|
|
852
|
+
mode: "sync",
|
|
853
|
+
status: result.exitCode === 0 ? "completed" : "failed",
|
|
854
|
+
providerRequested: "codex",
|
|
855
|
+
providerResolved: "codex",
|
|
719
856
|
agentType: args.agentType,
|
|
720
|
-
transport:
|
|
857
|
+
transport: "codex-mcp",
|
|
721
858
|
exitCode: result.exitCode,
|
|
722
859
|
output: result.output,
|
|
723
860
|
sessionKey: result.sessionKey,
|
|
@@ -725,7 +862,7 @@ export class DelegatorMcpWorker {
|
|
|
725
862
|
};
|
|
726
863
|
}
|
|
727
864
|
|
|
728
|
-
if (args.provider ===
|
|
865
|
+
if (args.provider === "gemini") {
|
|
729
866
|
const worker = this._createGeminiWorker();
|
|
730
867
|
const prompt = this._buildDirectPromptWithHint(args);
|
|
731
868
|
try {
|
|
@@ -733,18 +870,20 @@ export class DelegatorMcpWorker {
|
|
|
733
870
|
cwd: args.cwd || this.cwd,
|
|
734
871
|
timeoutMs: resolveTimeoutMs(args.agentType, args.timeoutMs),
|
|
735
872
|
model: args.model || resolveGeminiModel(args.agentType),
|
|
736
|
-
approvalMode:
|
|
737
|
-
allowedMcpServerNames: getGeminiAllowedServers(
|
|
873
|
+
approvalMode: "yolo",
|
|
874
|
+
allowedMcpServerNames: getGeminiAllowedServers(
|
|
875
|
+
this._getMcpPolicyOptions(args),
|
|
876
|
+
),
|
|
738
877
|
});
|
|
739
878
|
|
|
740
879
|
return {
|
|
741
880
|
ok: result.exitCode === 0,
|
|
742
|
-
mode:
|
|
743
|
-
status: result.exitCode === 0 ?
|
|
744
|
-
providerRequested:
|
|
745
|
-
providerResolved:
|
|
881
|
+
mode: "sync",
|
|
882
|
+
status: result.exitCode === 0 ? "completed" : "failed",
|
|
883
|
+
providerRequested: "gemini",
|
|
884
|
+
providerResolved: "gemini",
|
|
746
885
|
agentType: args.agentType,
|
|
747
|
-
transport:
|
|
886
|
+
transport: "gemini-worker",
|
|
748
887
|
exitCode: result.exitCode,
|
|
749
888
|
output: result.output,
|
|
750
889
|
sessionKey: result.sessionKey,
|
|
@@ -755,34 +894,42 @@ export class DelegatorMcpWorker {
|
|
|
755
894
|
}
|
|
756
895
|
}
|
|
757
896
|
|
|
758
|
-
return createErrorPayload(
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
897
|
+
return createErrorPayload(
|
|
898
|
+
`지원하지 않는 direct provider: ${args.provider}`,
|
|
899
|
+
{
|
|
900
|
+
mode: "sync",
|
|
901
|
+
providerRequested: args.provider,
|
|
902
|
+
agentType: args.agentType,
|
|
903
|
+
transport: "direct-worker",
|
|
904
|
+
},
|
|
905
|
+
);
|
|
764
906
|
}
|
|
765
907
|
|
|
766
908
|
async _executeRoute(args, extra) {
|
|
767
909
|
if (!this.routeScript) {
|
|
768
|
-
return createErrorPayload(
|
|
769
|
-
mode:
|
|
910
|
+
return createErrorPayload("tfx-route.sh 경로를 찾지 못했습니다.", {
|
|
911
|
+
mode: "sync",
|
|
770
912
|
providerRequested: args.provider,
|
|
771
913
|
agentType: args.agentType,
|
|
772
|
-
transport:
|
|
914
|
+
transport: "route-script",
|
|
773
915
|
});
|
|
774
916
|
}
|
|
775
917
|
|
|
776
|
-
await emitProgress(
|
|
918
|
+
await emitProgress(
|
|
919
|
+
extra,
|
|
920
|
+
DIRECT_PROGRESS_RUNNING,
|
|
921
|
+
100,
|
|
922
|
+
"tfx-route.sh 경로로 실행 중입니다.",
|
|
923
|
+
);
|
|
777
924
|
const result = await this._spawnRoute(args);
|
|
778
925
|
return {
|
|
779
926
|
ok: result.exitCode === 0,
|
|
780
|
-
mode:
|
|
781
|
-
status: result.exitCode === 0 ?
|
|
927
|
+
mode: "sync",
|
|
928
|
+
status: result.exitCode === 0 ? "completed" : "failed",
|
|
782
929
|
providerRequested: args.provider,
|
|
783
930
|
providerResolved: parseRouteType(result.stderr) || args.provider,
|
|
784
931
|
agentType: args.agentType,
|
|
785
|
-
transport:
|
|
932
|
+
transport: "route-script",
|
|
786
933
|
exitCode: result.exitCode,
|
|
787
934
|
output: result.stdout.trim() || result.stderr.trim(),
|
|
788
935
|
stderr: result.stderr.trim(),
|
|
@@ -790,10 +937,15 @@ export class DelegatorMcpWorker {
|
|
|
790
937
|
}
|
|
791
938
|
|
|
792
939
|
async _startAsyncJob(args, extra) {
|
|
793
|
-
const job = this._createJob(args,
|
|
940
|
+
const job = this._createJob(args, "async");
|
|
794
941
|
this.jobs.set(job.jobId, job);
|
|
795
942
|
|
|
796
|
-
await emitProgress(
|
|
943
|
+
await emitProgress(
|
|
944
|
+
extra,
|
|
945
|
+
DIRECT_PROGRESS_START,
|
|
946
|
+
100,
|
|
947
|
+
`비동기 job ${job.jobId}를 시작합니다.`,
|
|
948
|
+
);
|
|
797
949
|
|
|
798
950
|
void (async () => {
|
|
799
951
|
try {
|
|
@@ -802,15 +954,20 @@ export class DelegatorMcpWorker {
|
|
|
802
954
|
: await this._runAsyncWorker(args, job);
|
|
803
955
|
this._applyJobResult(job, result);
|
|
804
956
|
} catch (error) {
|
|
805
|
-
this._applyJobResult(
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
957
|
+
this._applyJobResult(
|
|
958
|
+
job,
|
|
959
|
+
createErrorPayload(
|
|
960
|
+
error instanceof Error ? error.message : String(error),
|
|
961
|
+
{
|
|
962
|
+
mode: "async",
|
|
963
|
+
providerRequested: args.provider,
|
|
964
|
+
agentType: args.agentType,
|
|
965
|
+
transport: this._shouldUseRoute(args)
|
|
966
|
+
? "route-script"
|
|
967
|
+
: `${args.provider}-worker`,
|
|
968
|
+
},
|
|
969
|
+
),
|
|
970
|
+
);
|
|
814
971
|
} finally {
|
|
815
972
|
if (job.worker) {
|
|
816
973
|
await job.worker.stop().catch(() => {});
|
|
@@ -824,29 +981,32 @@ export class DelegatorMcpWorker {
|
|
|
824
981
|
}
|
|
825
982
|
|
|
826
983
|
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
|
-
|
|
984
|
+
if (args.provider === "codex") {
|
|
985
|
+
const result = await this.codexWorker.execute(
|
|
986
|
+
this._buildDirectPrompt(args),
|
|
987
|
+
{
|
|
988
|
+
cwd: args.cwd || this.cwd,
|
|
989
|
+
timeoutMs: resolveTimeoutMs(args.agentType, args.timeoutMs),
|
|
990
|
+
sessionKey: args.sessionKey,
|
|
991
|
+
threadId: args.threadId,
|
|
992
|
+
resetSession: args.resetSession,
|
|
993
|
+
profile: resolveCodexProfile(args.agentType),
|
|
994
|
+
sandbox: "danger-full-access",
|
|
995
|
+
approvalPolicy: "never",
|
|
996
|
+
developerInstructions: joinInstructions(
|
|
997
|
+
REVIEW_INSTRUCTION_BY_AGENT[args.agentType],
|
|
998
|
+
this._buildPromptHintInstruction(args),
|
|
999
|
+
args.developerInstructions,
|
|
1000
|
+
),
|
|
1001
|
+
config: getCodexMcpConfig(this._getMcpPolicyOptions(args)),
|
|
1002
|
+
compactPrompt: args.compactPrompt,
|
|
1003
|
+
model: args.model,
|
|
1004
|
+
},
|
|
1005
|
+
);
|
|
846
1006
|
|
|
847
1007
|
return {
|
|
848
1008
|
ok: result.exitCode === 0,
|
|
849
|
-
providerResolved:
|
|
1009
|
+
providerResolved: "codex",
|
|
850
1010
|
output: result.output,
|
|
851
1011
|
exitCode: result.exitCode,
|
|
852
1012
|
threadId: result.threadId,
|
|
@@ -854,7 +1014,7 @@ export class DelegatorMcpWorker {
|
|
|
854
1014
|
};
|
|
855
1015
|
}
|
|
856
1016
|
|
|
857
|
-
if (args.provider ===
|
|
1017
|
+
if (args.provider === "gemini") {
|
|
858
1018
|
const worker = this._createGeminiWorker();
|
|
859
1019
|
job.worker = worker;
|
|
860
1020
|
const prompt = this._buildDirectPromptWithHint(args);
|
|
@@ -862,13 +1022,15 @@ export class DelegatorMcpWorker {
|
|
|
862
1022
|
cwd: args.cwd || this.cwd,
|
|
863
1023
|
timeoutMs: resolveTimeoutMs(args.agentType, args.timeoutMs),
|
|
864
1024
|
model: args.model || resolveGeminiModel(args.agentType),
|
|
865
|
-
approvalMode:
|
|
866
|
-
allowedMcpServerNames: getGeminiAllowedServers(
|
|
1025
|
+
approvalMode: "yolo",
|
|
1026
|
+
allowedMcpServerNames: getGeminiAllowedServers(
|
|
1027
|
+
this._getMcpPolicyOptions(args),
|
|
1028
|
+
),
|
|
867
1029
|
});
|
|
868
1030
|
|
|
869
1031
|
return {
|
|
870
1032
|
ok: result.exitCode === 0,
|
|
871
|
-
providerResolved:
|
|
1033
|
+
providerResolved: "gemini",
|
|
872
1034
|
output: result.output,
|
|
873
1035
|
exitCode: result.exitCode,
|
|
874
1036
|
sessionKey: result.sessionKey,
|
|
@@ -887,7 +1049,7 @@ export class DelegatorMcpWorker {
|
|
|
887
1049
|
env.TFX_CODEX_TRANSPORT = args.codexTransport;
|
|
888
1050
|
}
|
|
889
1051
|
if (args.noClaudeNative === true) {
|
|
890
|
-
env.TFX_NO_CLAUDE_NATIVE =
|
|
1052
|
+
env.TFX_NO_CLAUDE_NATIVE = "1";
|
|
891
1053
|
}
|
|
892
1054
|
if (args.searchTool) {
|
|
893
1055
|
env.TFX_SEARCH_TOOL = args.searchTool;
|
|
@@ -905,19 +1067,19 @@ export class DelegatorMcpWorker {
|
|
|
905
1067
|
}
|
|
906
1068
|
|
|
907
1069
|
async _spawnRoute(args, job = null) {
|
|
908
|
-
const prompt = withContext(String(args.prompt ??
|
|
1070
|
+
const prompt = withContext(String(args.prompt ?? ""), args.contextFile);
|
|
909
1071
|
const childArgs = [
|
|
910
1072
|
this.routeScript,
|
|
911
|
-
args.agentType ||
|
|
1073
|
+
args.agentType || "executor",
|
|
912
1074
|
prompt,
|
|
913
|
-
args.mcpProfile ||
|
|
1075
|
+
args.mcpProfile || "auto",
|
|
914
1076
|
String(resolveTimeoutSec(args.agentType, args.timeoutMs)),
|
|
915
1077
|
];
|
|
916
1078
|
|
|
917
1079
|
const child = spawn(this.bashCommand, childArgs, {
|
|
918
1080
|
cwd: args.cwd || this.cwd,
|
|
919
1081
|
env: this._buildRouteEnv(args),
|
|
920
|
-
stdio: [
|
|
1082
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
921
1083
|
windowsHide: true,
|
|
922
1084
|
});
|
|
923
1085
|
|
|
@@ -931,16 +1093,16 @@ export class DelegatorMcpWorker {
|
|
|
931
1093
|
const stdoutChunks = [];
|
|
932
1094
|
const stderrChunks = [];
|
|
933
1095
|
|
|
934
|
-
child.stdout.on(
|
|
935
|
-
child.stderr.on(
|
|
936
|
-
child.once(
|
|
1096
|
+
child.stdout.on("data", (chunk) => stdoutChunks.push(Buffer.from(chunk)));
|
|
1097
|
+
child.stderr.on("data", (chunk) => stderrChunks.push(Buffer.from(chunk)));
|
|
1098
|
+
child.once("error", (error) => {
|
|
937
1099
|
this.routeChildren.delete(child);
|
|
938
1100
|
rejectPromise(error);
|
|
939
1101
|
});
|
|
940
|
-
child.once(
|
|
1102
|
+
child.once("close", (code) => {
|
|
941
1103
|
this.routeChildren.delete(child);
|
|
942
|
-
const stdout = Buffer.concat(stdoutChunks).toString(
|
|
943
|
-
const stderr = Buffer.concat(stderrChunks).toString(
|
|
1104
|
+
const stdout = Buffer.concat(stdoutChunks).toString("utf8");
|
|
1105
|
+
const stderr = Buffer.concat(stderrChunks).toString("utf8");
|
|
944
1106
|
resolvePromise({
|
|
945
1107
|
ok: code === 0,
|
|
946
1108
|
providerResolved: parseRouteType(stderr) || args.provider,
|
|
@@ -957,7 +1119,7 @@ export class DelegatorMcpWorker {
|
|
|
957
1119
|
return {
|
|
958
1120
|
ok: job.ok,
|
|
959
1121
|
job_id: job.jobId,
|
|
960
|
-
mode: job.mode ||
|
|
1122
|
+
mode: job.mode || "async",
|
|
961
1123
|
status: job.status,
|
|
962
1124
|
provider_requested: job.providerRequested,
|
|
963
1125
|
provider_resolved: job.providerResolved,
|
|
@@ -969,7 +1131,7 @@ export class DelegatorMcpWorker {
|
|
|
969
1131
|
completed_at: job.completedAt,
|
|
970
1132
|
output: job.output,
|
|
971
1133
|
stderr: job.stderr,
|
|
972
|
-
error:
|
|
1134
|
+
error: "",
|
|
973
1135
|
thread_id: job.threadId,
|
|
974
1136
|
session_key: job.sessionKey,
|
|
975
1137
|
conversation_open: this.geminiConversations.has(job.jobId),
|
|
@@ -983,17 +1145,19 @@ export class DelegatorMcpWorker {
|
|
|
983
1145
|
ok: true,
|
|
984
1146
|
jobId,
|
|
985
1147
|
mode,
|
|
986
|
-
status:
|
|
1148
|
+
status: "running",
|
|
987
1149
|
providerRequested: args.provider,
|
|
988
1150
|
providerResolved: null,
|
|
989
1151
|
agentType: args.agentType,
|
|
990
|
-
transport: this._shouldUseRoute(args)
|
|
1152
|
+
transport: this._shouldUseRoute(args)
|
|
1153
|
+
? "route-script"
|
|
1154
|
+
: `${args.provider}-worker`,
|
|
991
1155
|
createdAt: now,
|
|
992
1156
|
startedAt: now,
|
|
993
1157
|
updatedAt: now,
|
|
994
1158
|
completedAt: null,
|
|
995
|
-
output:
|
|
996
|
-
stderr:
|
|
1159
|
+
output: "",
|
|
1160
|
+
stderr: "",
|
|
997
1161
|
exitCode: null,
|
|
998
1162
|
threadId: null,
|
|
999
1163
|
sessionKey: args.sessionKey || null,
|
|
@@ -1005,28 +1169,34 @@ export class DelegatorMcpWorker {
|
|
|
1005
1169
|
|
|
1006
1170
|
_applyJobResult(job, result = {}) {
|
|
1007
1171
|
job.ok = result.ok !== false;
|
|
1008
|
-
job.status = job.ok ?
|
|
1172
|
+
job.status = job.ok ? "completed" : "failed";
|
|
1009
1173
|
job.providerResolved = result.providerResolved || job.providerRequested;
|
|
1010
1174
|
job.transport = result.transport || job.transport;
|
|
1011
|
-
job.output = result.output ||
|
|
1012
|
-
job.stderr = result.stderr || result.error ||
|
|
1175
|
+
job.output = result.output || "";
|
|
1176
|
+
job.stderr = result.stderr || result.error || "";
|
|
1013
1177
|
job.exitCode = result.exitCode ?? (job.ok ? 0 : 1);
|
|
1014
1178
|
job.threadId = result.threadId || job.threadId || null;
|
|
1015
1179
|
job.sessionKey = result.sessionKey || job.sessionKey || null;
|
|
1016
1180
|
job.completedAt = new Date().toISOString();
|
|
1017
1181
|
job.updatedAt = job.completedAt;
|
|
1018
1182
|
|
|
1019
|
-
if (
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1183
|
+
if (
|
|
1184
|
+
job.providerRequested === "gemini" &&
|
|
1185
|
+
job.transport === "gemini-worker" &&
|
|
1186
|
+
typeof result._geminiPrompt === "string"
|
|
1187
|
+
) {
|
|
1188
|
+
this._storeGeminiConversation(
|
|
1189
|
+
job,
|
|
1190
|
+
result._geminiPrompt,
|
|
1191
|
+
result.output || "",
|
|
1192
|
+
);
|
|
1023
1193
|
}
|
|
1024
1194
|
}
|
|
1025
1195
|
|
|
1026
1196
|
_storeGeminiConversation(job, userPrompt, assistantReply) {
|
|
1027
1197
|
const existing = this.geminiConversations.get(job.jobId);
|
|
1028
1198
|
if (existing) {
|
|
1029
|
-
if (typeof assistantReply ===
|
|
1199
|
+
if (typeof assistantReply === "string") {
|
|
1030
1200
|
const lastTurn = existing.turns.at(-1);
|
|
1031
1201
|
if (lastTurn && lastTurn.assistant !== assistantReply) {
|
|
1032
1202
|
lastTurn.assistant = assistantReply;
|
|
@@ -1040,15 +1210,17 @@ export class DelegatorMcpWorker {
|
|
|
1040
1210
|
jobId: job.jobId,
|
|
1041
1211
|
closed: false,
|
|
1042
1212
|
updatedAt: new Date().toISOString(),
|
|
1043
|
-
turns: [
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1213
|
+
turns: [
|
|
1214
|
+
{
|
|
1215
|
+
user: userPrompt,
|
|
1216
|
+
assistant: assistantReply,
|
|
1217
|
+
},
|
|
1218
|
+
],
|
|
1047
1219
|
});
|
|
1048
1220
|
}
|
|
1049
1221
|
|
|
1050
1222
|
async _runSyncJob(args, extra) {
|
|
1051
|
-
const job = this._createJob(args,
|
|
1223
|
+
const job = this._createJob(args, "sync");
|
|
1052
1224
|
this.jobs.set(job.jobId, job);
|
|
1053
1225
|
const result = await this._executeDirect(args, extra);
|
|
1054
1226
|
this._applyJobResult(job, result);
|
|
@@ -1065,7 +1237,9 @@ export async function runDelegatorMcpCli() {
|
|
|
1065
1237
|
try {
|
|
1066
1238
|
await worker.serveStdio();
|
|
1067
1239
|
} catch (error) {
|
|
1068
|
-
console.error(
|
|
1240
|
+
console.error(
|
|
1241
|
+
`[delegator-mcp] ${error instanceof Error ? error.message : String(error)}`,
|
|
1242
|
+
);
|
|
1069
1243
|
process.exitCode = 1;
|
|
1070
1244
|
}
|
|
1071
1245
|
}
|