triflux 3.2.0-dev.3 → 3.2.0-dev.5

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.
@@ -1,36 +1,36 @@
1
- #!/usr/bin/env bash
2
- # tfx-route.sh v2.0 — CLI 라우팅 래퍼 (triflux)
3
- #
4
- # v1.x: cli-route.sh (jq+python3+node 혼재, 동기 후처리 ~1s)
5
- # v2.0: tfx-route.sh 리네임
6
- # - 후처리 전부 tfx-route-post.mjs로 이관 (node 단일 ~100ms)
7
- # - per-process 에이전트 등록 (race condition 구조적 제거)
8
- # - get_mcp_hint 통합 (캐시/비캐시 단일 코드경로)
9
- # - Gemini health check 지수 백오프 (30×1s → 5×exp)
10
- # - 컨텍스트 파일 5번째 인자 지원
11
- #
12
- VERSION="2.0"
13
- #
14
- # 사용법:
15
- # tfx-route.sh <agent_type> <prompt> [mcp_profile] [timeout_sec] [context_file]
16
- #
17
- # 예시:
18
- # tfx-route.sh executor "코드 구현" implement
19
- # tfx-route.sh architect "아키텍처 분석" analyze '' context.md
20
-
21
- set -euo pipefail
22
-
23
- # ── 인자 파싱 ──
24
- AGENT_TYPE="${1:?에이전트 타입 필수 (executor, debugger, designer 등)}"
25
- PROMPT="${2:?프롬프트 필수}"
26
- MCP_PROFILE="${3:-auto}"
27
- USER_TIMEOUT="${4:-}"
28
- CONTEXT_FILE="${5:-}"
29
-
30
- # ── CLI 경로 해석 (Windows npm global 대응) ──
31
- CODEX_BIN="${CODEX_BIN:-$(command -v codex 2>/dev/null || echo codex)}"
32
- GEMINI_BIN="${GEMINI_BIN:-$(command -v gemini 2>/dev/null || echo gemini)}"
33
-
1
+ #!/usr/bin/env bash
2
+ # tfx-route.sh v2.0 — CLI 라우팅 래퍼 (triflux)
3
+ #
4
+ # v1.x: cli-route.sh (jq+python3+node 혼재, 동기 후처리 ~1s)
5
+ # v2.0: tfx-route.sh 리네임
6
+ # - 후처리 전부 tfx-route-post.mjs로 이관 (node 단일 ~100ms)
7
+ # - per-process 에이전트 등록 (race condition 구조적 제거)
8
+ # - get_mcp_hint 통합 (캐시/비캐시 단일 코드경로)
9
+ # - Gemini health check 지수 백오프 (30×1s → 5×exp)
10
+ # - 컨텍스트 파일 5번째 인자 지원
11
+ #
12
+ VERSION="2.0"
13
+ #
14
+ # 사용법:
15
+ # tfx-route.sh <agent_type> <prompt> [mcp_profile] [timeout_sec] [context_file]
16
+ #
17
+ # 예시:
18
+ # tfx-route.sh executor "코드 구현" implement
19
+ # tfx-route.sh architect "아키텍처 분석" analyze '' context.md
20
+
21
+ set -euo pipefail
22
+
23
+ # ── 인자 파싱 ──
24
+ AGENT_TYPE="${1:?에이전트 타입 필수 (executor, debugger, designer 등)}"
25
+ PROMPT="${2:?프롬프트 필수}"
26
+ MCP_PROFILE="${3:-auto}"
27
+ USER_TIMEOUT="${4:-}"
28
+ CONTEXT_FILE="${5:-}"
29
+
30
+ # ── CLI 경로 해석 (Windows npm global 대응) ──
31
+ CODEX_BIN="${CODEX_BIN:-$(command -v codex 2>/dev/null || echo codex)}"
32
+ GEMINI_BIN="${GEMINI_BIN:-$(command -v gemini 2>/dev/null || echo gemini)}"
33
+
34
34
  # ── 상수 ──
35
35
  MAX_STDOUT_BYTES=51200 # 50KB — Claude 컨텍스트 절약
36
36
  TIMESTAMP=$(date +%s)
@@ -44,18 +44,18 @@ TFX_TEAM_TASK_ID="${TFX_TEAM_TASK_ID:-}"
44
44
  TFX_TEAM_AGENT_NAME="${TFX_TEAM_AGENT_NAME:-${AGENT_TYPE}-worker-$$}"
45
45
  TFX_TEAM_LEAD_NAME="${TFX_TEAM_LEAD_NAME:-team-lead}"
46
46
  TFX_HUB_URL="${TFX_HUB_URL:-http://127.0.0.1:27888}"
47
-
48
- # fallback 시 원래 에이전트 정보 보존
49
- ORIGINAL_AGENT=""
50
- ORIGINAL_CLI_ARGS=""
51
-
52
- # ── Per-process 에이전트 등록 (원자적, 락 불필요) ──
53
- register_agent() {
54
- local agent_file="${TFX_TMP}/tfx-agent-$$.json"
55
- echo "{\"pid\":$$,\"cli\":\"$CLI_TYPE\",\"agent\":\"$AGENT_TYPE\",\"started\":$(date +%s)}" \
56
- > "$agent_file" 2>/dev/null || true
57
- }
58
-
47
+
48
+ # fallback 시 원래 에이전트 정보 보존
49
+ ORIGINAL_AGENT=""
50
+ ORIGINAL_CLI_ARGS=""
51
+
52
+ # ── Per-process 에이전트 등록 (원자적, 락 불필요) ──
53
+ register_agent() {
54
+ local agent_file="${TFX_TMP}/tfx-agent-$$.json"
55
+ echo "{\"pid\":$$,\"cli\":\"$CLI_TYPE\",\"agent\":\"$AGENT_TYPE\",\"started\":$(date +%s)}" \
56
+ > "$agent_file" 2>/dev/null || true
57
+ }
58
+
59
59
  deregister_agent() {
60
60
  rm -f "${TFX_TMP}/tfx-agent-$$.json" 2>/dev/null || true
61
61
  }
@@ -133,301 +133,365 @@ team_complete_task() {
133
133
  # ── 라우팅 테이블 ──
134
134
  # 반환: CLI_CMD, CLI_ARGS, CLI_TYPE, CLI_EFFORT, DEFAULT_TIMEOUT, RUN_MODE, OPUS_OVERSIGHT
135
135
  route_agent() {
136
- local agent="$1"
137
- local codex_base="--dangerously-bypass-approvals-and-sandbox --skip-git-repo-check"
138
-
139
- case "$agent" in
140
- # ─── 구현 레인 ───
141
- executor)
142
- CLI_TYPE="codex"; CLI_CMD="codex"
143
- CLI_ARGS="exec ${codex_base}"
144
- CLI_EFFORT="high"; DEFAULT_TIMEOUT=1080; RUN_MODE="fg"; OPUS_OVERSIGHT="false" ;;
145
- build-fixer)
146
- CLI_TYPE="codex"; CLI_CMD="codex"
147
- CLI_ARGS="exec --profile fast ${codex_base}"
148
- CLI_EFFORT="fast"; DEFAULT_TIMEOUT=540; RUN_MODE="fg"; OPUS_OVERSIGHT="false" ;;
149
- debugger)
150
- CLI_TYPE="codex"; CLI_CMD="codex"
151
- CLI_ARGS="exec ${codex_base}"
152
- CLI_EFFORT="high"; DEFAULT_TIMEOUT=900; RUN_MODE="bg"; OPUS_OVERSIGHT="false" ;;
153
- deep-executor)
154
- CLI_TYPE="codex"; CLI_CMD="codex"
155
- CLI_ARGS="exec --profile xhigh ${codex_base}"
156
- CLI_EFFORT="xhigh"; DEFAULT_TIMEOUT=3600; RUN_MODE="bg"; OPUS_OVERSIGHT="true" ;;
157
-
158
- # ─── 설계/분석 레인 ───
159
- architect)
160
- CLI_TYPE="codex"; CLI_CMD="codex"
161
- CLI_ARGS="exec --profile xhigh ${codex_base}"
162
- CLI_EFFORT="xhigh"; DEFAULT_TIMEOUT=3600; RUN_MODE="bg"; OPUS_OVERSIGHT="true" ;;
163
- planner)
164
- CLI_TYPE="codex"; CLI_CMD="codex"
165
- CLI_ARGS="exec --profile xhigh ${codex_base}"
166
- CLI_EFFORT="xhigh"; DEFAULT_TIMEOUT=3600; RUN_MODE="fg"; OPUS_OVERSIGHT="true" ;;
167
- critic)
168
- CLI_TYPE="codex"; CLI_CMD="codex"
169
- CLI_ARGS="exec --profile xhigh ${codex_base}"
170
- CLI_EFFORT="xhigh"; DEFAULT_TIMEOUT=3600; RUN_MODE="bg"; OPUS_OVERSIGHT="true" ;;
171
- analyst)
172
- CLI_TYPE="codex"; CLI_CMD="codex"
173
- CLI_ARGS="exec --profile xhigh ${codex_base}"
174
- CLI_EFFORT="xhigh"; DEFAULT_TIMEOUT=3600; RUN_MODE="fg"; OPUS_OVERSIGHT="true" ;;
175
-
176
- # ─── 리뷰 레인 ───
177
- code-reviewer)
178
- CLI_TYPE="codex"; CLI_CMD="codex"
179
- CLI_ARGS="exec --profile thorough ${codex_base} review"
180
- CLI_EFFORT="thorough"; DEFAULT_TIMEOUT=1800; RUN_MODE="bg"; OPUS_OVERSIGHT="false" ;;
181
- security-reviewer)
182
- CLI_TYPE="codex"; CLI_CMD="codex"
183
- CLI_ARGS="exec --profile thorough ${codex_base} review"
184
- CLI_EFFORT="thorough"; DEFAULT_TIMEOUT=1800; RUN_MODE="bg"; OPUS_OVERSIGHT="true" ;;
185
- quality-reviewer)
186
- CLI_TYPE="codex"; CLI_CMD="codex"
187
- CLI_ARGS="exec --profile thorough ${codex_base} review"
188
- CLI_EFFORT="thorough"; DEFAULT_TIMEOUT=1800; RUN_MODE="bg"; OPUS_OVERSIGHT="false" ;;
189
-
190
- # ─── 리서치 레인 ───
191
- scientist)
192
- CLI_TYPE="codex"; CLI_CMD="codex"
193
- CLI_ARGS="exec ${codex_base}"
194
- CLI_EFFORT="high"; DEFAULT_TIMEOUT=1440; RUN_MODE="bg"; OPUS_OVERSIGHT="false" ;;
195
- scientist-deep)
196
- CLI_TYPE="codex"; CLI_CMD="codex"
197
- CLI_ARGS="exec --profile thorough ${codex_base}"
198
- CLI_EFFORT="thorough"; DEFAULT_TIMEOUT=3600; RUN_MODE="bg"; OPUS_OVERSIGHT="false" ;;
199
- document-specialist)
200
- CLI_TYPE="codex"; CLI_CMD="codex"
201
- CLI_ARGS="exec ${codex_base}"
202
- CLI_EFFORT="high"; DEFAULT_TIMEOUT=1440; RUN_MODE="bg"; OPUS_OVERSIGHT="false" ;;
203
-
204
- # ─── UI/문서 레인 ───
205
- designer)
206
- CLI_TYPE="gemini"; CLI_CMD="gemini"
207
- CLI_ARGS="-m gemini-3.1-pro-preview -y --prompt"
208
- CLI_EFFORT="pro"; DEFAULT_TIMEOUT=900; RUN_MODE="bg"; OPUS_OVERSIGHT="false" ;;
209
- writer)
210
- CLI_TYPE="gemini"; CLI_CMD="gemini"
211
- CLI_ARGS="-m gemini-3-flash-preview -y --prompt"
212
- CLI_EFFORT="flash"; DEFAULT_TIMEOUT=900; RUN_MODE="bg"; OPUS_OVERSIGHT="false" ;;
213
-
214
- # ─── Claude 네이티브 ───
215
- explore)
216
- CLI_TYPE="claude-native"; CLI_CMD=""; CLI_ARGS=""
217
- CLI_EFFORT="n/a"; DEFAULT_TIMEOUT=300; RUN_MODE="fg"; OPUS_OVERSIGHT="false" ;;
218
- verifier)
219
- CLI_TYPE="claude-native"; CLI_CMD=""; CLI_ARGS=""
220
- CLI_EFFORT="n/a"; DEFAULT_TIMEOUT=300; RUN_MODE="fg"; OPUS_OVERSIGHT="false" ;;
221
- test-engineer)
222
- CLI_TYPE="claude-native"; CLI_CMD=""; CLI_ARGS=""
223
- CLI_EFFORT="n/a"; DEFAULT_TIMEOUT=300; RUN_MODE="bg"; OPUS_OVERSIGHT="false" ;;
224
- qa-tester)
225
- CLI_TYPE="claude-native"; CLI_CMD=""; CLI_ARGS=""
226
- CLI_EFFORT="n/a"; DEFAULT_TIMEOUT=300; RUN_MODE="bg"; OPUS_OVERSIGHT="false" ;;
227
-
228
- # ─── 경량 ───
229
- spark)
230
- CLI_TYPE="codex"; CLI_CMD="codex"
231
- CLI_ARGS="exec --profile spark_fast ${codex_base}"
232
- CLI_EFFORT="spark_fast"; DEFAULT_TIMEOUT=180; RUN_MODE="fg"; OPUS_OVERSIGHT="false" ;;
233
- *)
234
- echo "ERROR: 알 수 없는 에이전트 타입: $agent" >&2
235
- echo "사용 가능: executor, build-fixer, debugger, deep-executor, architect, planner, critic, analyst," >&2
236
- echo " code-reviewer, security-reviewer, quality-reviewer, scientist, document-specialist," >&2
237
- echo " designer, writer, explore, verifier, test-engineer, qa-tester, spark" >&2
238
- exit 1 ;;
239
- esac
240
- }
241
-
242
- # ── CLI 모드 오버라이드 (tfx-codex / tfx-gemini 스킬용) ──
243
- TFX_CLI_MODE="${TFX_CLI_MODE:-auto}"
244
-
245
- apply_cli_mode() {
246
- local codex_base="--dangerously-bypass-approvals-and-sandbox --skip-git-repo-check"
247
-
248
- case "$TFX_CLI_MODE" in
249
- codex)
250
- if [[ "$CLI_TYPE" == "gemini" ]]; then
251
- CLI_TYPE="codex"; CLI_CMD="codex"
252
- case "$AGENT_TYPE" in
253
- designer)
254
- CLI_ARGS="exec ${codex_base}"; CLI_EFFORT="high"; DEFAULT_TIMEOUT=600 ;;
255
- writer)
256
- CLI_ARGS="exec --profile spark_fast ${codex_base}"; CLI_EFFORT="spark_fast"; DEFAULT_TIMEOUT=180 ;;
257
- esac
258
- echo "[tfx-route] TFX_CLI_MODE=codex: $AGENT_TYPE codex($CLI_EFFORT)로 리매핑" >&2
259
- fi ;;
260
- gemini)
261
- if [[ "$CLI_TYPE" == "codex" ]]; then
262
- CLI_TYPE="gemini"; CLI_CMD="gemini"
263
- case "$AGENT_TYPE" in
264
- executor|debugger|deep-executor|architect|planner|critic|analyst|\
265
- code-reviewer|security-reviewer|quality-reviewer|scientist-deep)
266
- CLI_ARGS="-m gemini-3.1-pro-preview -y --prompt"; CLI_EFFORT="pro" ;;
267
- build-fixer|spark)
268
- CLI_ARGS="-m gemini-3-flash-preview -y --prompt"; CLI_EFFORT="flash"; DEFAULT_TIMEOUT=180 ;;
269
- *)
270
- CLI_ARGS="-m gemini-3-flash-preview -y --prompt"; CLI_EFFORT="flash" ;;
271
- esac
272
- echo "[tfx-route] TFX_CLI_MODE=gemini: $AGENT_TYPE → gemini($CLI_EFFORT)로 리매핑" >&2
273
- fi ;;
274
- auto)
275
- if [[ "$CLI_TYPE" == "codex" ]] && ! command -v "$CODEX_BIN" &>/dev/null; then
276
- if command -v "$GEMINI_BIN" &>/dev/null; then
277
- TFX_CLI_MODE="gemini"; apply_cli_mode; return
278
- else
279
- ORIGINAL_AGENT="${AGENT_TYPE}"; ORIGINAL_CLI_ARGS="$CLI_ARGS"
280
- CLI_TYPE="claude-native"; CLI_CMD=""; CLI_ARGS=""
281
- echo "[tfx-route] codex/gemini 모두 미설치: $AGENT_TYPE → claude-native fallback" >&2
282
- fi
283
- elif [[ "$CLI_TYPE" == "gemini" ]] && ! command -v "$GEMINI_BIN" &>/dev/null; then
284
- if command -v "$CODEX_BIN" &>/dev/null; then
285
- TFX_CLI_MODE="codex"; apply_cli_mode; return
286
- else
287
- ORIGINAL_AGENT="${AGENT_TYPE}"; ORIGINAL_CLI_ARGS="$CLI_ARGS"
288
- CLI_TYPE="claude-native"; CLI_CMD=""; CLI_ARGS=""
289
- echo "[tfx-route] codex/gemini 모두 미설치: $AGENT_TYPE → claude-native fallback" >&2
290
- fi
291
- fi ;;
292
- esac
293
- }
294
-
295
- # ── MCP 인벤토리 캐시 ──
296
- MCP_CACHE="${HOME}/.claude/cache/mcp-inventory.json"
297
-
298
- get_cached_servers() {
299
- local cli_type="$1"
300
- if [[ -f "$MCP_CACHE" ]]; then
301
- node -e 'const[,f,t]=process.argv;const inv=JSON.parse(require("fs").readFileSync(f,"utf8"));const s=(inv[t]||{}).servers||[];console.log(s.filter(x=>x.status==="enabled"||x.status==="configured").map(x=>x.name).join(","))' -- "$MCP_CACHE" "$cli_type" 2>/dev/null
302
- fi
303
- }
304
-
305
- # ── MCP 프로필 → 프롬프트 힌트 (통합: 캐시 유무 단일 코드경로) ──
306
- get_mcp_hint() {
307
- local profile="$1"
308
- local agent="$2"
309
-
310
- # auto → 구체 프로필 해석
311
- if [[ "$profile" == "auto" ]]; then
312
- case "$agent" in
313
- executor|build-fixer|debugger|deep-executor) profile="implement" ;;
314
- architect|planner|critic|analyst) profile="analyze" ;;
315
- code-reviewer|security-reviewer|quality-reviewer) profile="review" ;;
316
- scientist|document-specialist) profile="analyze" ;;
317
- designer|writer) profile="docs" ;;
318
- *) profile="minimal" ;;
319
- esac
320
- fi
321
-
322
- # 서버 목록: 캐시 있으면 실제, 없으면 전부 가용 가정 (기존 비캐시 동작과 동일)
323
- local servers
324
- servers=$(get_cached_servers "$CLI_TYPE")
325
- [[ -z "$servers" ]] && servers="context7,brave-search,exa,tavily,playwright,sequential-thinking"
326
-
327
- has_server() { echo ",$servers," | grep -q ",$1,"; }
328
-
329
- local hint=""
330
- case "$profile" in
331
- implement)
332
- has_server "context7" && hint+="context7으로 라이브러리 문서를 조회하세요. "
333
- if has_server "brave-search"; then hint+="웹 검색은 brave-search를 사용하세요. "
334
- elif has_server "exa"; then hint+="웹 검색은 exa를 사용하세요. "
335
- elif has_server "tavily"; then hint+="웹 검색은 tavily를 사용하세요. "
336
- fi
337
- hint+="검색 도구 실패 시 재시도하지 말고 다음 도구로 전환하세요."
338
- ;;
339
- analyze)
340
- has_server "context7" && hint+="context7으로 관련 문서를 조회하세요. "
341
- local search_tools=""
342
- has_server "brave-search" && search_tools+="brave-search, "
343
- has_server "tavily" && search_tools+="tavily, "
344
- has_server "exa" && search_tools+="exa, "
345
- [[ -n "$search_tools" ]] && hint+="웹 검색 우선순위: ${search_tools%, }. 402 에러 시 즉시 다음 도구로 전환. "
346
- has_server "playwright" && hint+="모든 검색 실패 시 playwright로 직접 방문 (최대 3 URL). "
347
- hint+="검색 깊이를 제한하고 결과를 빠르게 요약하세요."
348
- ;;
349
- review)
350
- has_server "sequential-thinking" && hint="sequential-thinking으로 체계적으로 분석하세요."
351
- ;;
352
- docs)
353
- has_server "context7" && hint+="context7으로 공식 문서를 참조하세요. "
354
- has_server "brave-search" && hint+="추가 검색은 brave-search를 사용하세요. "
355
- hint+="검색 결과의 출처 URL을 함께 제시하세요."
356
- ;;
357
- minimal|none) ;;
358
- esac
359
- echo "$hint"
360
- }
361
-
362
- # ── Gemini MCP 서버 선택적 로드 ──
363
- get_gemini_mcp_filter() {
364
- local profile="$1"
365
- case "$profile" in
366
- implement) echo "--allowed-mcp-server-names context7,brave-search" ;;
367
- analyze) echo "--allowed-mcp-server-names context7,brave-search,exa" ;;
368
- review) echo "--allowed-mcp-server-names sequential-thinking" ;;
369
- docs) echo "--allowed-mcp-server-names context7,brave-search" ;;
370
- *) echo "" ;;
371
- esac
372
- }
373
-
374
- # ── 메인 실행 ──
375
- main() {
376
- # 종료 시 per-process 에이전트 파일 자동 삭제
377
- trap 'deregister_agent' EXIT
378
-
379
- route_agent "$AGENT_TYPE"
380
- apply_cli_mode
381
-
382
- # CLI 경로 해석
383
- case "$CLI_CMD" in
384
- codex) CLI_CMD="$CODEX_BIN" ;;
385
- gemini) CLI_CMD="$GEMINI_BIN" ;;
386
- esac
387
-
388
- # 타임아웃 결정
389
- if [[ -n "$USER_TIMEOUT" ]]; then
390
- TIMEOUT_SEC="$USER_TIMEOUT"
391
- else
392
- TIMEOUT_SEC="$DEFAULT_TIMEOUT"
393
- fi
394
-
395
- # 컨텍스트 파일 프롬프트에 주입
396
- if [[ -n "$CONTEXT_FILE" && -f "$CONTEXT_FILE" ]]; then
397
- local ctx_content
398
- ctx_content=$(cat "$CONTEXT_FILE" 2>/dev/null | head -c 32768) # 32KB 상한
399
- PROMPT="${PROMPT}
400
-
401
- <prior_context>
402
- ${ctx_content}
403
- </prior_context>"
404
- fi
405
-
406
- # Claude 네이티브 에이전트는 이 스크립트로 처리 불가 → 메타데이터만 출력
407
- if [[ "$CLI_TYPE" == "claude-native" ]]; then
408
- local model="sonnet"
409
- case "$AGENT_TYPE" in
410
- explore) model="haiku" ;;
411
- esac
412
- echo "ROUTE_TYPE=claude-native"
413
- echo "AGENT=$AGENT_TYPE"
414
- echo "MODEL=$model"
415
- echo "RUN_MODE=$RUN_MODE"
416
- echo "OPUS_OVERSIGHT=$OPUS_OVERSIGHT"
417
- echo "TIMEOUT=$TIMEOUT_SEC"
418
- echo "MCP_PROFILE=$MCP_PROFILE"
419
- [[ -n "$ORIGINAL_AGENT" ]] && echo "ORIGINAL_AGENT=$ORIGINAL_AGENT"
420
- echo "PROMPT=$PROMPT"
421
- echo "--- Claude Task($model) 에이전트로 위임하세요 ---"
422
- exit 0
423
- fi
424
-
425
- # MCP 힌트 주입
426
- local mcp_hint
427
- mcp_hint=$(get_mcp_hint "$MCP_PROFILE" "$AGENT_TYPE")
428
- local FULL_PROMPT="$PROMPT"
429
- [[ -n "$mcp_hint" ]] && FULL_PROMPT="${PROMPT}. ${mcp_hint}"
430
-
136
+ local agent="$1"
137
+ local codex_base="--dangerously-bypass-approvals-and-sandbox --skip-git-repo-check"
138
+
139
+ case "$agent" in
140
+ # ─── 구현 레인 ───
141
+ executor)
142
+ CLI_TYPE="codex"; CLI_CMD="codex"
143
+ CLI_ARGS="exec ${codex_base}"
144
+ CLI_EFFORT="high"; DEFAULT_TIMEOUT=1080; RUN_MODE="fg"; OPUS_OVERSIGHT="false" ;;
145
+ build-fixer)
146
+ CLI_TYPE="codex"; CLI_CMD="codex"
147
+ CLI_ARGS="exec --profile fast ${codex_base}"
148
+ CLI_EFFORT="fast"; DEFAULT_TIMEOUT=540; RUN_MODE="fg"; OPUS_OVERSIGHT="false" ;;
149
+ debugger)
150
+ CLI_TYPE="codex"; CLI_CMD="codex"
151
+ CLI_ARGS="exec ${codex_base}"
152
+ CLI_EFFORT="high"; DEFAULT_TIMEOUT=900; RUN_MODE="bg"; OPUS_OVERSIGHT="false" ;;
153
+ deep-executor)
154
+ CLI_TYPE="codex"; CLI_CMD="codex"
155
+ CLI_ARGS="exec --profile xhigh ${codex_base}"
156
+ CLI_EFFORT="xhigh"; DEFAULT_TIMEOUT=3600; RUN_MODE="bg"; OPUS_OVERSIGHT="true" ;;
157
+
158
+ # ─── 설계/분석 레인 ───
159
+ architect)
160
+ CLI_TYPE="codex"; CLI_CMD="codex"
161
+ CLI_ARGS="exec --profile xhigh ${codex_base}"
162
+ CLI_EFFORT="xhigh"; DEFAULT_TIMEOUT=3600; RUN_MODE="bg"; OPUS_OVERSIGHT="true" ;;
163
+ planner)
164
+ CLI_TYPE="codex"; CLI_CMD="codex"
165
+ CLI_ARGS="exec --profile xhigh ${codex_base}"
166
+ CLI_EFFORT="xhigh"; DEFAULT_TIMEOUT=3600; RUN_MODE="fg"; OPUS_OVERSIGHT="true" ;;
167
+ critic)
168
+ CLI_TYPE="codex"; CLI_CMD="codex"
169
+ CLI_ARGS="exec --profile xhigh ${codex_base}"
170
+ CLI_EFFORT="xhigh"; DEFAULT_TIMEOUT=3600; RUN_MODE="bg"; OPUS_OVERSIGHT="true" ;;
171
+ analyst)
172
+ CLI_TYPE="codex"; CLI_CMD="codex"
173
+ CLI_ARGS="exec --profile xhigh ${codex_base}"
174
+ CLI_EFFORT="xhigh"; DEFAULT_TIMEOUT=3600; RUN_MODE="fg"; OPUS_OVERSIGHT="true" ;;
175
+
176
+ # ─── 리뷰 레인 ───
177
+ code-reviewer)
178
+ CLI_TYPE="codex"; CLI_CMD="codex"
179
+ CLI_ARGS="exec --profile thorough ${codex_base} review"
180
+ CLI_EFFORT="thorough"; DEFAULT_TIMEOUT=1800; RUN_MODE="bg"; OPUS_OVERSIGHT="false" ;;
181
+ security-reviewer)
182
+ CLI_TYPE="codex"; CLI_CMD="codex"
183
+ CLI_ARGS="exec --profile thorough ${codex_base} review"
184
+ CLI_EFFORT="thorough"; DEFAULT_TIMEOUT=1800; RUN_MODE="bg"; OPUS_OVERSIGHT="true" ;;
185
+ quality-reviewer)
186
+ CLI_TYPE="codex"; CLI_CMD="codex"
187
+ CLI_ARGS="exec --profile thorough ${codex_base} review"
188
+ CLI_EFFORT="thorough"; DEFAULT_TIMEOUT=1800; RUN_MODE="bg"; OPUS_OVERSIGHT="false" ;;
189
+
190
+ # ─── 리서치 레인 ───
191
+ scientist)
192
+ CLI_TYPE="codex"; CLI_CMD="codex"
193
+ CLI_ARGS="exec ${codex_base}"
194
+ CLI_EFFORT="high"; DEFAULT_TIMEOUT=1440; RUN_MODE="bg"; OPUS_OVERSIGHT="false" ;;
195
+ scientist-deep)
196
+ CLI_TYPE="codex"; CLI_CMD="codex"
197
+ CLI_ARGS="exec --profile thorough ${codex_base}"
198
+ CLI_EFFORT="thorough"; DEFAULT_TIMEOUT=3600; RUN_MODE="bg"; OPUS_OVERSIGHT="false" ;;
199
+ document-specialist)
200
+ CLI_TYPE="codex"; CLI_CMD="codex"
201
+ CLI_ARGS="exec ${codex_base}"
202
+ CLI_EFFORT="high"; DEFAULT_TIMEOUT=1440; RUN_MODE="bg"; OPUS_OVERSIGHT="false" ;;
203
+
204
+ # ─── UI/문서 레인 ───
205
+ designer)
206
+ CLI_TYPE="gemini"; CLI_CMD="gemini"
207
+ CLI_ARGS="-m gemini-3.1-pro-preview -y --prompt"
208
+ CLI_EFFORT="pro"; DEFAULT_TIMEOUT=900; RUN_MODE="bg"; OPUS_OVERSIGHT="false" ;;
209
+ writer)
210
+ CLI_TYPE="gemini"; CLI_CMD="gemini"
211
+ CLI_ARGS="-m gemini-3-flash-preview -y --prompt"
212
+ CLI_EFFORT="flash"; DEFAULT_TIMEOUT=900; RUN_MODE="bg"; OPUS_OVERSIGHT="false" ;;
213
+
214
+ # ─── Claude 네이티브 ───
215
+ explore)
216
+ CLI_TYPE="claude-native"; CLI_CMD=""; CLI_ARGS=""
217
+ CLI_EFFORT="n/a"; DEFAULT_TIMEOUT=300; RUN_MODE="fg"; OPUS_OVERSIGHT="false" ;;
218
+ verifier)
219
+ CLI_TYPE="claude-native"; CLI_CMD=""; CLI_ARGS=""
220
+ CLI_EFFORT="n/a"; DEFAULT_TIMEOUT=300; RUN_MODE="fg"; OPUS_OVERSIGHT="false" ;;
221
+ test-engineer)
222
+ CLI_TYPE="claude-native"; CLI_CMD=""; CLI_ARGS=""
223
+ CLI_EFFORT="n/a"; DEFAULT_TIMEOUT=300; RUN_MODE="bg"; OPUS_OVERSIGHT="false" ;;
224
+ qa-tester)
225
+ CLI_TYPE="claude-native"; CLI_CMD=""; CLI_ARGS=""
226
+ CLI_EFFORT="n/a"; DEFAULT_TIMEOUT=300; RUN_MODE="bg"; OPUS_OVERSIGHT="false" ;;
227
+
228
+ # ─── 경량 ───
229
+ spark)
230
+ CLI_TYPE="codex"; CLI_CMD="codex"
231
+ CLI_ARGS="exec --profile spark_fast ${codex_base}"
232
+ CLI_EFFORT="spark_fast"; DEFAULT_TIMEOUT=180; RUN_MODE="fg"; OPUS_OVERSIGHT="false" ;;
233
+ *)
234
+ echo "ERROR: 알 수 없는 에이전트 타입: $agent" >&2
235
+ echo "사용 가능: executor, build-fixer, debugger, deep-executor, architect, planner, critic, analyst," >&2
236
+ echo " code-reviewer, security-reviewer, quality-reviewer, scientist, document-specialist," >&2
237
+ echo " designer, writer, explore, verifier, test-engineer, qa-tester, spark" >&2
238
+ exit 1 ;;
239
+ esac
240
+ }
241
+
242
+ # ── CLI 모드 오버라이드 (tfx-codex / tfx-gemini 스킬용) ──
243
+ TFX_CLI_MODE="${TFX_CLI_MODE:-auto}"
244
+ TFX_NO_CLAUDE_NATIVE="${TFX_NO_CLAUDE_NATIVE:-0}"
245
+ case "$TFX_NO_CLAUDE_NATIVE" in
246
+ 0|1) ;;
247
+ *)
248
+ echo "ERROR: TFX_NO_CLAUDE_NATIVE 값은 0 또는 1이어야 합니다. (현재: $TFX_NO_CLAUDE_NATIVE)" >&2
249
+ exit 1
250
+ ;;
251
+ esac
252
+
253
+ apply_cli_mode() {
254
+ local codex_base="--dangerously-bypass-approvals-and-sandbox --skip-git-repo-check"
255
+
256
+ case "$TFX_CLI_MODE" in
257
+ codex)
258
+ if [[ "$CLI_TYPE" == "gemini" ]]; then
259
+ CLI_TYPE="codex"; CLI_CMD="codex"
260
+ case "$AGENT_TYPE" in
261
+ designer)
262
+ CLI_ARGS="exec ${codex_base}"; CLI_EFFORT="high"; DEFAULT_TIMEOUT=600 ;;
263
+ writer)
264
+ CLI_ARGS="exec --profile spark_fast ${codex_base}"; CLI_EFFORT="spark_fast"; DEFAULT_TIMEOUT=180 ;;
265
+ esac
266
+ echo "[tfx-route] TFX_CLI_MODE=codex: $AGENT_TYPE codex($CLI_EFFORT)로 리매핑" >&2
267
+ fi ;;
268
+ gemini)
269
+ if [[ "$CLI_TYPE" == "codex" ]]; then
270
+ CLI_TYPE="gemini"; CLI_CMD="gemini"
271
+ case "$AGENT_TYPE" in
272
+ executor|debugger|deep-executor|architect|planner|critic|analyst|\
273
+ code-reviewer|security-reviewer|quality-reviewer|scientist-deep)
274
+ CLI_ARGS="-m gemini-3.1-pro-preview -y --prompt"; CLI_EFFORT="pro" ;;
275
+ build-fixer|spark)
276
+ CLI_ARGS="-m gemini-3-flash-preview -y --prompt"; CLI_EFFORT="flash"; DEFAULT_TIMEOUT=180 ;;
277
+ *)
278
+ CLI_ARGS="-m gemini-3-flash-preview -y --prompt"; CLI_EFFORT="flash" ;;
279
+ esac
280
+ echo "[tfx-route] TFX_CLI_MODE=gemini: $AGENT_TYPE → gemini($CLI_EFFORT)로 리매핑" >&2
281
+ fi ;;
282
+ auto)
283
+ if [[ "$CLI_TYPE" == "codex" ]] && ! command -v "$CODEX_BIN" &>/dev/null; then
284
+ if command -v "$GEMINI_BIN" &>/dev/null; then
285
+ TFX_CLI_MODE="gemini"; apply_cli_mode; return
286
+ else
287
+ ORIGINAL_AGENT="${AGENT_TYPE}"; ORIGINAL_CLI_ARGS="$CLI_ARGS"
288
+ CLI_TYPE="claude-native"; CLI_CMD=""; CLI_ARGS=""
289
+ echo "[tfx-route] codex/gemini 모두 미설치: $AGENT_TYPE → claude-native fallback" >&2
290
+ fi
291
+ elif [[ "$CLI_TYPE" == "gemini" ]] && ! command -v "$GEMINI_BIN" &>/dev/null; then
292
+ if command -v "$CODEX_BIN" &>/dev/null; then
293
+ TFX_CLI_MODE="codex"; apply_cli_mode; return
294
+ else
295
+ ORIGINAL_AGENT="${AGENT_TYPE}"; ORIGINAL_CLI_ARGS="$CLI_ARGS"
296
+ CLI_TYPE="claude-native"; CLI_CMD=""; CLI_ARGS=""
297
+ echo "[tfx-route] codex/gemini 모두 미설치: $AGENT_TYPE → claude-native fallback" >&2
298
+ fi
299
+ fi ;;
300
+ esac
301
+ }
302
+
303
+ # ── Claude 네이티브 제거 (Codex 리드 환경에서 선택적 활성화) ──
304
+ apply_no_claude_native_mode() {
305
+ local codex_base="--dangerously-bypass-approvals-and-sandbox --skip-git-repo-check"
306
+
307
+ [[ "$TFX_NO_CLAUDE_NATIVE" != "1" ]] && return
308
+ [[ "$TFX_CLI_MODE" == "gemini" ]] && return
309
+ [[ "$CLI_TYPE" != "claude-native" ]] && return
310
+
311
+ if ! command -v "$CODEX_BIN" &>/dev/null; then
312
+ echo "[tfx-route] TFX_NO_CLAUDE_NATIVE=1 이지만 codex를 찾지 못해 claude-native 유지" >&2
313
+ return
314
+ fi
315
+
316
+ ORIGINAL_AGENT="${AGENT_TYPE}"
317
+ CLI_TYPE="codex"; CLI_CMD="codex"
318
+
319
+ case "$AGENT_TYPE" in
320
+ explore)
321
+ CLI_ARGS="exec --profile fast ${codex_base}"
322
+ CLI_EFFORT="fast"
323
+ DEFAULT_TIMEOUT=600
324
+ RUN_MODE="fg"
325
+ OPUS_OVERSIGHT="false"
326
+ ;;
327
+ verifier)
328
+ CLI_ARGS="exec --profile thorough ${codex_base} review"
329
+ CLI_EFFORT="thorough"
330
+ DEFAULT_TIMEOUT=1200
331
+ RUN_MODE="fg"
332
+ OPUS_OVERSIGHT="false"
333
+ ;;
334
+ test-engineer)
335
+ CLI_ARGS="exec ${codex_base}"
336
+ CLI_EFFORT="high"
337
+ DEFAULT_TIMEOUT=1200
338
+ RUN_MODE="bg"
339
+ OPUS_OVERSIGHT="false"
340
+ ;;
341
+ qa-tester)
342
+ CLI_ARGS="exec --profile thorough ${codex_base} review"
343
+ CLI_EFFORT="thorough"
344
+ DEFAULT_TIMEOUT=1200
345
+ RUN_MODE="bg"
346
+ OPUS_OVERSIGHT="false"
347
+ ;;
348
+ *)
349
+ # claude-native 타입 중 위에 없는 경우는 보수적으로 유지
350
+ CLI_TYPE="claude-native"; CLI_CMD=""; CLI_ARGS=""
351
+ return
352
+ ;;
353
+ esac
354
+
355
+ echo "[tfx-route] TFX_NO_CLAUDE_NATIVE=1: $AGENT_TYPE -> codex($CLI_EFFORT) 리매핑" >&2
356
+ }
357
+
358
+ # ── MCP 인벤토리 캐시 ──
359
+ MCP_CACHE="${HOME}/.claude/cache/mcp-inventory.json"
360
+
361
+ get_cached_servers() {
362
+ local cli_type="$1"
363
+ if [[ -f "$MCP_CACHE" ]]; then
364
+ node -e 'const[,f,t]=process.argv;const inv=JSON.parse(require("fs").readFileSync(f,"utf8"));const s=(inv[t]||{}).servers||[];console.log(s.filter(x=>x.status==="enabled"||x.status==="configured").map(x=>x.name).join(","))' -- "$MCP_CACHE" "$cli_type" 2>/dev/null
365
+ fi
366
+ }
367
+
368
+ # ── MCP 프로필 → 프롬프트 힌트 (통합: 캐시 유무 단일 코드경로) ──
369
+ get_mcp_hint() {
370
+ local profile="$1"
371
+ local agent="$2"
372
+
373
+ # auto → 구체 프로필 해석
374
+ if [[ "$profile" == "auto" ]]; then
375
+ case "$agent" in
376
+ executor|build-fixer|debugger|deep-executor) profile="implement" ;;
377
+ architect|planner|critic|analyst) profile="analyze" ;;
378
+ code-reviewer|security-reviewer|quality-reviewer) profile="review" ;;
379
+ scientist|document-specialist) profile="analyze" ;;
380
+ designer|writer) profile="docs" ;;
381
+ *) profile="minimal" ;;
382
+ esac
383
+ fi
384
+
385
+ # 서버 목록: 캐시 있으면 실제, 없으면 전부 가용 가정 (기존 비캐시 동작과 동일)
386
+ local servers
387
+ servers=$(get_cached_servers "$CLI_TYPE")
388
+ [[ -z "$servers" ]] && servers="context7,brave-search,exa,tavily,playwright,sequential-thinking"
389
+
390
+ has_server() { echo ",$servers," | grep -q ",$1,"; }
391
+
392
+ local hint=""
393
+ case "$profile" in
394
+ implement)
395
+ has_server "context7" && hint+="context7으로 라이브러리 문서를 조회하세요. "
396
+ if has_server "brave-search"; then hint+=" 검색은 brave-search를 사용하세요. "
397
+ elif has_server "exa"; then hint+="웹 검색은 exa를 사용하세요. "
398
+ elif has_server "tavily"; then hint+="웹 검색은 tavily를 사용하세요. "
399
+ fi
400
+ hint+="검색 도구 실패 시 재시도하지 말고 다음 도구로 전환하세요."
401
+ ;;
402
+ analyze)
403
+ has_server "context7" && hint+="context7으로 관련 문서를 조회하세요. "
404
+ local search_tools=""
405
+ has_server "brave-search" && search_tools+="brave-search, "
406
+ has_server "tavily" && search_tools+="tavily, "
407
+ has_server "exa" && search_tools+="exa, "
408
+ [[ -n "$search_tools" ]] && hint+="웹 검색 우선순위: ${search_tools%, }. 402 에러 시 즉시 다음 도구로 전환. "
409
+ has_server "playwright" && hint+="모든 검색 실패 시 playwright로 직접 방문 (최대 3 URL). "
410
+ hint+="검색 깊이를 제한하고 결과를 빠르게 요약하세요."
411
+ ;;
412
+ review)
413
+ has_server "sequential-thinking" && hint="sequential-thinking으로 체계적으로 분석하세요."
414
+ ;;
415
+ docs)
416
+ has_server "context7" && hint+="context7으로 공식 문서를 참조하세요. "
417
+ has_server "brave-search" && hint+="추가 검색은 brave-search를 사용하세요. "
418
+ hint+="검색 결과의 출처 URL을 함께 제시하세요."
419
+ ;;
420
+ minimal|none) ;;
421
+ esac
422
+ echo "$hint"
423
+ }
424
+
425
+ # ── Gemini MCP 서버 선택적 로드 ──
426
+ get_gemini_mcp_filter() {
427
+ local profile="$1"
428
+ case "$profile" in
429
+ implement) echo "--allowed-mcp-server-names context7,brave-search" ;;
430
+ analyze) echo "--allowed-mcp-server-names context7,brave-search,exa" ;;
431
+ review) echo "--allowed-mcp-server-names sequential-thinking" ;;
432
+ docs) echo "--allowed-mcp-server-names context7,brave-search" ;;
433
+ *) echo "" ;;
434
+ esac
435
+ }
436
+
437
+ # ── 메인 실행 ──
438
+ main() {
439
+ # 종료 시 per-process 에이전트 파일 자동 삭제
440
+ trap 'deregister_agent' EXIT
441
+
442
+ route_agent "$AGENT_TYPE"
443
+ apply_cli_mode
444
+ apply_no_claude_native_mode
445
+
446
+ # CLI 경로 해석
447
+ case "$CLI_CMD" in
448
+ codex) CLI_CMD="$CODEX_BIN" ;;
449
+ gemini) CLI_CMD="$GEMINI_BIN" ;;
450
+ esac
451
+
452
+ # 타임아웃 결정
453
+ if [[ -n "$USER_TIMEOUT" ]]; then
454
+ TIMEOUT_SEC="$USER_TIMEOUT"
455
+ else
456
+ TIMEOUT_SEC="$DEFAULT_TIMEOUT"
457
+ fi
458
+
459
+ # 컨텍스트 파일 → 프롬프트에 주입
460
+ if [[ -n "$CONTEXT_FILE" && -f "$CONTEXT_FILE" ]]; then
461
+ local ctx_content
462
+ ctx_content=$(cat "$CONTEXT_FILE" 2>/dev/null | head -c 32768) # 32KB 상한
463
+ PROMPT="${PROMPT}
464
+
465
+ <prior_context>
466
+ ${ctx_content}
467
+ </prior_context>"
468
+ fi
469
+
470
+ # Claude 네이티브 에이전트는 이 스크립트로 처리 불가 → 메타데이터만 출력
471
+ if [[ "$CLI_TYPE" == "claude-native" ]]; then
472
+ local model="sonnet"
473
+ case "$AGENT_TYPE" in
474
+ explore) model="haiku" ;;
475
+ esac
476
+ echo "ROUTE_TYPE=claude-native"
477
+ echo "AGENT=$AGENT_TYPE"
478
+ echo "MODEL=$model"
479
+ echo "RUN_MODE=$RUN_MODE"
480
+ echo "OPUS_OVERSIGHT=$OPUS_OVERSIGHT"
481
+ echo "TIMEOUT=$TIMEOUT_SEC"
482
+ echo "MCP_PROFILE=$MCP_PROFILE"
483
+ [[ -n "$ORIGINAL_AGENT" ]] && echo "ORIGINAL_AGENT=$ORIGINAL_AGENT"
484
+ echo "PROMPT=$PROMPT"
485
+ echo "--- Claude Task($model) 에이전트로 위임하세요 ---"
486
+ exit 0
487
+ fi
488
+
489
+ # MCP 힌트 주입
490
+ local mcp_hint
491
+ mcp_hint=$(get_mcp_hint "$MCP_PROFILE" "$AGENT_TYPE")
492
+ local FULL_PROMPT="$PROMPT"
493
+ [[ -n "$mcp_hint" ]] && FULL_PROMPT="${PROMPT}. ${mcp_hint}"
494
+
431
495
  # 메타정보 (stderr)
432
496
  echo "[tfx-route] v${VERSION} type=$CLI_TYPE agent=$AGENT_TYPE effort=$CLI_EFFORT mode=$RUN_MODE timeout=${TIMEOUT_SEC}s" >&2
433
497
  echo "[tfx-route] opus_oversight=$OPUS_OVERSIGHT mcp_profile=$MCP_PROFILE" >&2
@@ -442,55 +506,55 @@ ${ctx_content}
442
506
  # CLI 실행 (stderr 분리 + 타임아웃 + 소요시간 측정)
443
507
  local exit_code=0
444
508
  local start_time
445
- start_time=$(date +%s)
446
-
447
- if [[ "$CLI_TYPE" == "codex" ]]; then
448
- # Codex: stdout/stderr 모두 파일로 캡처 (post.mjs가 읽음)
449
- timeout "$TIMEOUT_SEC" $CLI_CMD $CLI_ARGS "$FULL_PROMPT" >"$STDOUT_LOG" 2>"$STDERR_LOG" || exit_code=$?
450
-
451
- elif [[ "$CLI_TYPE" == "gemini" ]]; then
452
- # Gemini: MCP 프로필별 서버 필터
453
- local gemini_mcp_filter
454
- gemini_mcp_filter=$(get_gemini_mcp_filter "$MCP_PROFILE")
455
- local gemini_args="$CLI_ARGS"
456
- if [[ -n "$gemini_mcp_filter" ]]; then
457
- gemini_args="${CLI_ARGS/--prompt/$gemini_mcp_filter --prompt}"
458
- echo "[tfx-route] Gemini MCP 필터: $gemini_mcp_filter" >&2
459
- fi
460
-
461
- timeout "$TIMEOUT_SEC" $CLI_CMD $gemini_args "$FULL_PROMPT" >"$STDOUT_LOG" 2>"$STDERR_LOG" &
462
- local pid=$!
463
-
464
- # 지수 백오프 health check (v1.x: 30×1s → v2.0: 5×exp, 총 19초)
465
- local health_ok=true
466
- local intervals=(1 2 3 5 8)
467
- for wait_sec in "${intervals[@]}"; do
468
- sleep "$wait_sec"
469
- # 출력 있으면 정상 → 조기 탈출
470
- if [[ -s "$STDOUT_LOG" ]] || [[ -s "$STDERR_LOG" ]]; then
471
- break
472
- fi
473
- # 프로세스 사망 + 출력 없음 → crash
474
- if ! kill -0 "$pid" 2>/dev/null; then
475
- health_ok=false
476
- echo "[tfx-route] Gemini: 출력 없이 프로세스 종료 (${wait_sec}초 체크)" >&2
477
- break
478
- fi
479
- done
480
-
481
- if [[ "$health_ok" == "false" ]]; then
482
- wait "$pid" 2>/dev/null
483
- echo "[tfx-route] Gemini crash 감지, 재시도 중..." >&2
484
- timeout "$TIMEOUT_SEC" $CLI_CMD $gemini_args "$FULL_PROMPT" >"$STDOUT_LOG" 2>"$STDERR_LOG" &
485
- pid=$!
486
- wait "$pid"
487
- exit_code=$?
488
- else
489
- wait "$pid"
490
- exit_code=$?
491
- fi
492
- fi
493
-
509
+ start_time=$(date +%s)
510
+
511
+ if [[ "$CLI_TYPE" == "codex" ]]; then
512
+ # Codex: stdout/stderr 모두 파일로 캡처 (post.mjs가 읽음)
513
+ timeout "$TIMEOUT_SEC" $CLI_CMD $CLI_ARGS "$FULL_PROMPT" >"$STDOUT_LOG" 2>"$STDERR_LOG" || exit_code=$?
514
+
515
+ elif [[ "$CLI_TYPE" == "gemini" ]]; then
516
+ # Gemini: MCP 프로필별 서버 필터
517
+ local gemini_mcp_filter
518
+ gemini_mcp_filter=$(get_gemini_mcp_filter "$MCP_PROFILE")
519
+ local gemini_args="$CLI_ARGS"
520
+ if [[ -n "$gemini_mcp_filter" ]]; then
521
+ gemini_args="${CLI_ARGS/--prompt/$gemini_mcp_filter --prompt}"
522
+ echo "[tfx-route] Gemini MCP 필터: $gemini_mcp_filter" >&2
523
+ fi
524
+
525
+ timeout "$TIMEOUT_SEC" $CLI_CMD $gemini_args "$FULL_PROMPT" >"$STDOUT_LOG" 2>"$STDERR_LOG" &
526
+ local pid=$!
527
+
528
+ # 지수 백오프 health check (v1.x: 30×1s → v2.0: 5×exp, 총 19초)
529
+ local health_ok=true
530
+ local intervals=(1 2 3 5 8)
531
+ for wait_sec in "${intervals[@]}"; do
532
+ sleep "$wait_sec"
533
+ # 출력 있으면 정상 → 조기 탈출
534
+ if [[ -s "$STDOUT_LOG" ]] || [[ -s "$STDERR_LOG" ]]; then
535
+ break
536
+ fi
537
+ # 프로세스 사망 + 출력 없음 → crash
538
+ if ! kill -0 "$pid" 2>/dev/null; then
539
+ health_ok=false
540
+ echo "[tfx-route] Gemini: 출력 없이 프로세스 종료 (${wait_sec}초 체크)" >&2
541
+ break
542
+ fi
543
+ done
544
+
545
+ if [[ "$health_ok" == "false" ]]; then
546
+ wait "$pid" 2>/dev/null
547
+ echo "[tfx-route] Gemini crash 감지, 재시도 중..." >&2
548
+ timeout "$TIMEOUT_SEC" $CLI_CMD $gemini_args "$FULL_PROMPT" >"$STDOUT_LOG" 2>"$STDERR_LOG" &
549
+ pid=$!
550
+ wait "$pid"
551
+ exit_code=$?
552
+ else
553
+ wait "$pid"
554
+ exit_code=$?
555
+ fi
556
+ fi
557
+
494
558
  local end_time
495
559
  end_time=$(date +%s)
496
560
  local elapsed=$((end_time - start_time))
@@ -513,32 +577,32 @@ ${ctx_content}
513
577
  # ── 후처리: 단일 node 프로세스로 위임 ──
514
578
  # 토큰 추출, 출력 필터링, 로그, 토큰 누적, AIMD, 이슈 추적, 결과 출력 전부 처리
515
579
  local post_script="${HOME}/.claude/scripts/tfx-route-post.mjs"
516
- if [[ -f "$post_script" ]]; then
517
- node "$post_script" \
518
- --agent "$AGENT_TYPE" \
519
- --cli "$CLI_TYPE" \
520
- --cli-cmd "$CLI_CMD" \
521
- --effort "$CLI_EFFORT" \
522
- --run-mode "$RUN_MODE" \
523
- --opus "$OPUS_OVERSIGHT" \
524
- --exit-code "$exit_code" \
525
- --elapsed "$elapsed" \
526
- --timeout "$TIMEOUT_SEC" \
527
- --mcp-profile "$MCP_PROFILE" \
528
- --stderr-log "$STDERR_LOG" \
529
- --stdout-log "$STDOUT_LOG" \
530
- --max-bytes "$MAX_STDOUT_BYTES"
531
- else
532
- # post.mjs 없으면 기본 출력 (fallback)
533
- echo "=== TFX-ROUTE RESULT ==="
534
- echo "agent: $AGENT_TYPE"
535
- echo "cli: $CLI_TYPE"
536
- echo "exit_code: $exit_code"
537
- echo "elapsed: ${elapsed}s"
538
- echo "status: $([ $exit_code -eq 0 ] && echo success || echo failed)"
539
- echo "=== OUTPUT ==="
540
- cat "$STDOUT_LOG" 2>/dev/null | head -c "$MAX_STDOUT_BYTES"
541
- fi
542
- }
543
-
544
- main
580
+ if [[ -f "$post_script" ]]; then
581
+ node "$post_script" \
582
+ --agent "$AGENT_TYPE" \
583
+ --cli "$CLI_TYPE" \
584
+ --cli-cmd "$CLI_CMD" \
585
+ --effort "$CLI_EFFORT" \
586
+ --run-mode "$RUN_MODE" \
587
+ --opus "$OPUS_OVERSIGHT" \
588
+ --exit-code "$exit_code" \
589
+ --elapsed "$elapsed" \
590
+ --timeout "$TIMEOUT_SEC" \
591
+ --mcp-profile "$MCP_PROFILE" \
592
+ --stderr-log "$STDERR_LOG" \
593
+ --stdout-log "$STDOUT_LOG" \
594
+ --max-bytes "$MAX_STDOUT_BYTES"
595
+ else
596
+ # post.mjs 없으면 기본 출력 (fallback)
597
+ echo "=== TFX-ROUTE RESULT ==="
598
+ echo "agent: $AGENT_TYPE"
599
+ echo "cli: $CLI_TYPE"
600
+ echo "exit_code: $exit_code"
601
+ echo "elapsed: ${elapsed}s"
602
+ echo "status: $([ $exit_code -eq 0 ] && echo success || echo failed)"
603
+ echo "=== OUTPUT ==="
604
+ cat "$STDOUT_LOG" 2>/dev/null | head -c "$MAX_STDOUT_BYTES"
605
+ fi
606
+ }
607
+
608
+ main