triflux 3.2.0-dev.8 → 3.3.0-dev.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/triflux.mjs +1296 -1055
- package/hooks/hooks.json +17 -0
- package/hooks/keyword-rules.json +20 -4
- package/hooks/pipeline-stop.mjs +54 -0
- package/hub/bridge.mjs +517 -318
- package/hub/hitl.mjs +45 -31
- package/hub/pipe.mjs +457 -0
- package/hub/pipeline/index.mjs +121 -0
- package/hub/pipeline/state.mjs +164 -0
- package/hub/pipeline/transitions.mjs +114 -0
- package/hub/router.mjs +422 -161
- package/hub/schema.sql +14 -0
- package/hub/server.mjs +499 -424
- package/hub/store.mjs +388 -314
- package/hub/team/cli-team-common.mjs +348 -0
- package/hub/team/cli-team-control.mjs +393 -0
- package/hub/team/cli-team-start.mjs +516 -0
- package/hub/team/cli-team-status.mjs +269 -0
- package/hub/team/cli.mjs +75 -1475
- package/hub/team/dashboard.mjs +1 -9
- package/hub/team/native.mjs +190 -130
- package/hub/team/nativeProxy.mjs +165 -78
- package/hub/team/orchestrator.mjs +15 -20
- package/hub/team/pane.mjs +137 -103
- package/hub/team/psmux.mjs +506 -0
- package/hub/team/session.mjs +393 -330
- package/hub/team/shared.mjs +13 -0
- package/hub/team/staleState.mjs +299 -0
- package/hub/tools.mjs +105 -31
- package/hub/workers/claude-worker.mjs +446 -0
- package/hub/workers/codex-mcp.mjs +414 -0
- package/hub/workers/factory.mjs +18 -0
- package/hub/workers/gemini-worker.mjs +349 -0
- package/hub/workers/interface.mjs +41 -0
- package/hud/hud-qos-status.mjs +1790 -1788
- package/package.json +4 -1
- package/scripts/__tests__/keyword-detector.test.mjs +8 -8
- package/scripts/keyword-detector.mjs +15 -0
- package/scripts/lib/keyword-rules.mjs +4 -1
- package/scripts/preflight-cache.mjs +72 -0
- package/scripts/psmux-steering-prototype.sh +368 -0
- package/scripts/setup.mjs +136 -71
- package/scripts/tfx-route-worker.mjs +161 -0
- package/scripts/tfx-route.sh +485 -91
- package/skills/tfx-auto/SKILL.md +90 -564
- package/skills/tfx-auto-codex/SKILL.md +1 -3
- package/skills/tfx-codex/SKILL.md +1 -4
- package/skills/tfx-doctor/SKILL.md +1 -0
- package/skills/tfx-gemini/SKILL.md +1 -4
- package/skills/tfx-multi/SKILL.md +378 -0
- package/skills/tfx-setup/SKILL.md +1 -4
- package/skills/tfx-team/SKILL.md +0 -304
package/scripts/tfx-route.sh
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# tfx-route.sh v2.
|
|
2
|
+
# tfx-route.sh v2.3 — CLI 라우팅 래퍼 (triflux)
|
|
3
3
|
#
|
|
4
4
|
# v1.x: cli-route.sh (jq+python3+node 혼재, 동기 후처리 ~1s)
|
|
5
5
|
# v2.0: tfx-route.sh 리네임
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
# - Gemini health check 지수 백오프 (30×1s → 5×exp)
|
|
10
10
|
# - 컨텍스트 파일 5번째 인자 지원
|
|
11
11
|
#
|
|
12
|
-
VERSION="2.
|
|
12
|
+
VERSION="2.3"
|
|
13
13
|
#
|
|
14
14
|
# 사용법:
|
|
15
15
|
# tfx-route.sh <agent_type> <prompt> [mcp_profile] [timeout_sec] [context_file]
|
|
@@ -28,14 +28,19 @@ USER_TIMEOUT="${4:-}"
|
|
|
28
28
|
CONTEXT_FILE="${5:-}"
|
|
29
29
|
|
|
30
30
|
# ── CLI 경로 해석 (Windows npm global 대응) ──
|
|
31
|
+
NODE_BIN="${NODE_BIN:-$(command -v node 2>/dev/null || echo node)}"
|
|
31
32
|
CODEX_BIN="${CODEX_BIN:-$(command -v codex 2>/dev/null || echo codex)}"
|
|
32
33
|
GEMINI_BIN="${GEMINI_BIN:-$(command -v gemini 2>/dev/null || echo gemini)}"
|
|
34
|
+
CLAUDE_BIN="${CLAUDE_BIN:-$(command -v claude 2>/dev/null || echo claude)}"
|
|
35
|
+
GEMINI_BIN_ARGS_JSON="${GEMINI_BIN_ARGS_JSON:-[]}"
|
|
36
|
+
CLAUDE_BIN_ARGS_JSON="${CLAUDE_BIN_ARGS_JSON:-[]}"
|
|
33
37
|
|
|
34
38
|
# ── 상수 ──
|
|
35
39
|
MAX_STDOUT_BYTES=51200 # 50KB — Claude 컨텍스트 절약
|
|
36
40
|
TIMESTAMP=$(date +%s)
|
|
37
|
-
|
|
38
|
-
|
|
41
|
+
RUN_ID="${TIMESTAMP}-$$-${RANDOM}"
|
|
42
|
+
STDERR_LOG="/tmp/tfx-route-${AGENT_TYPE}-${RUN_ID}-stderr.log"
|
|
43
|
+
STDOUT_LOG="/tmp/tfx-route-${AGENT_TYPE}-${RUN_ID}-stdout.log"
|
|
39
44
|
TFX_TMP="${TMPDIR:-/tmp}"
|
|
40
45
|
|
|
41
46
|
# ── 팀 환경변수 ──
|
|
@@ -64,12 +69,18 @@ deregister_agent() {
|
|
|
64
69
|
# JSON 문자열 이스케이프 (큰따옴표, 백슬래시, 개행, 탭, CR)
|
|
65
70
|
json_escape() {
|
|
66
71
|
local s="${1:-}"
|
|
72
|
+
# node로 완전한 JSON 이스케이프 (NUL, 멀티바이트 UTF-8, 제어문자 안전)
|
|
73
|
+
if command -v node &>/dev/null; then
|
|
74
|
+
node -e 'process.stdout.write(JSON.stringify(process.argv[1]).slice(1,-1))' -- "$s"
|
|
75
|
+
return
|
|
76
|
+
fi
|
|
77
|
+
# node 미설치 fallback: 기본 Bash 치환
|
|
67
78
|
s="${s//\\/\\\\}"
|
|
68
79
|
s="${s//\"/\\\"}"
|
|
69
80
|
s="${s//$'\n'/\\n}"
|
|
70
81
|
s="${s//$'\t'/\\t}"
|
|
71
82
|
s="${s//$'\r'/\\r}"
|
|
72
|
-
|
|
83
|
+
printf '%s' "$s"
|
|
73
84
|
}
|
|
74
85
|
|
|
75
86
|
team_claim_task() {
|
|
@@ -97,36 +108,35 @@ team_claim_task() {
|
|
|
97
108
|
}
|
|
98
109
|
|
|
99
110
|
team_complete_task() {
|
|
100
|
-
local
|
|
111
|
+
local result="${1:-success}" # success/failed/timeout
|
|
101
112
|
local result_summary="${2:-작업 완료}"
|
|
102
|
-
local safe_team_name safe_task_id safe_agent_name safe_status
|
|
103
113
|
[[ -z "$TFX_TEAM_NAME" || -z "$TFX_TEAM_TASK_ID" ]] && return 0
|
|
114
|
+
|
|
115
|
+
local safe_team_name safe_task_id safe_agent_name safe_result safe_summary safe_lead_name
|
|
104
116
|
safe_team_name=$(json_escape "$TFX_TEAM_NAME")
|
|
105
117
|
safe_task_id=$(json_escape "$TFX_TEAM_TASK_ID")
|
|
106
118
|
safe_agent_name=$(json_escape "$TFX_TEAM_AGENT_NAME")
|
|
107
|
-
|
|
119
|
+
safe_result=$(json_escape "$result")
|
|
120
|
+
safe_summary=$(json_escape "$(echo "$result_summary" | head -c 4096)")
|
|
121
|
+
safe_lead_name=$(json_escape "$TFX_TEAM_LEAD_NAME")
|
|
108
122
|
|
|
109
|
-
# task
|
|
123
|
+
# task 상태: 항상 "completed" (Claude Code API는 "failed" 미지원)
|
|
124
|
+
# 실제 결과는 metadata.result로 전달
|
|
110
125
|
curl -sf -X POST "${TFX_HUB_URL}/bridge/team/task-update" \
|
|
111
126
|
-H "Content-Type: application/json" \
|
|
112
|
-
-d "{\"team_name\":\"${safe_team_name}\",\"task_id\":\"${safe_task_id}\",\"status\":\"${
|
|
127
|
+
-d "{\"team_name\":\"${safe_team_name}\",\"task_id\":\"${safe_task_id}\",\"status\":\"completed\",\"owner\":\"${safe_agent_name}\",\"metadata_patch\":{\"result\":\"${safe_result}\",\"summary\":\"${safe_summary}\"}}" \
|
|
113
128
|
>/dev/null 2>&1 || true
|
|
114
129
|
|
|
115
130
|
# 리드에게 메시지 전송
|
|
116
|
-
local msg_text safe_text safe_lead_name
|
|
117
|
-
msg_text=$(echo "$result_summary" | head -c 4096)
|
|
118
|
-
safe_text=$(json_escape "$msg_text")
|
|
119
|
-
safe_lead_name=$(json_escape "$TFX_TEAM_LEAD_NAME")
|
|
120
|
-
|
|
121
131
|
curl -sf -X POST "${TFX_HUB_URL}/bridge/team/send-message" \
|
|
122
132
|
-H "Content-Type: application/json" \
|
|
123
|
-
-d "{\"team_name\":\"${safe_team_name}\",\"from\":\"${safe_agent_name}\",\"to\":\"${safe_lead_name}\",\"text\":\"${
|
|
133
|
+
-d "{\"team_name\":\"${safe_team_name}\",\"from\":\"${safe_agent_name}\",\"to\":\"${safe_lead_name}\",\"text\":\"${safe_summary}\",\"summary\":\"task ${safe_task_id} ${safe_result}\"}" \
|
|
124
134
|
>/dev/null 2>&1 || true
|
|
125
135
|
|
|
126
136
|
# Hub result 발행 (poll_messages 채널 활성화)
|
|
127
137
|
curl -sf -X POST "${TFX_HUB_URL}/bridge/result" \
|
|
128
138
|
-H "Content-Type: application/json" \
|
|
129
|
-
-d "{\"agent_id\":\"${safe_agent_name}\",\"topic\":\"task.result\",\"payload\":{\"task_id\":\"${safe_task_id}\",\"
|
|
139
|
+
-d "{\"agent_id\":\"${safe_agent_name}\",\"topic\":\"task.result\",\"payload\":{\"task_id\":\"${safe_task_id}\",\"result\":\"${safe_result}\"},\"trace_id\":\"${safe_team_name}\"}" \
|
|
130
140
|
>/dev/null 2>&1 || true
|
|
131
141
|
}
|
|
132
142
|
|
|
@@ -216,8 +226,9 @@ route_agent() {
|
|
|
216
226
|
CLI_TYPE="claude-native"; CLI_CMD=""; CLI_ARGS=""
|
|
217
227
|
CLI_EFFORT="n/a"; DEFAULT_TIMEOUT=300; RUN_MODE="fg"; OPUS_OVERSIGHT="false" ;;
|
|
218
228
|
verifier)
|
|
219
|
-
CLI_TYPE="
|
|
220
|
-
|
|
229
|
+
CLI_TYPE="codex"; CLI_CMD="codex"
|
|
230
|
+
CLI_ARGS="exec --profile thorough ${codex_base} review"
|
|
231
|
+
CLI_EFFORT="thorough"; DEFAULT_TIMEOUT=1200; RUN_MODE="fg"; OPUS_OVERSIGHT="false" ;;
|
|
221
232
|
test-engineer)
|
|
222
233
|
CLI_TYPE="claude-native"; CLI_CMD=""; CLI_ARGS=""
|
|
223
234
|
CLI_EFFORT="n/a"; DEFAULT_TIMEOUT=300; RUN_MODE="bg"; OPUS_OVERSIGHT="false" ;;
|
|
@@ -242,6 +253,9 @@ route_agent() {
|
|
|
242
253
|
# ── CLI 모드 오버라이드 (tfx-codex / tfx-gemini 스킬용) ──
|
|
243
254
|
TFX_CLI_MODE="${TFX_CLI_MODE:-auto}"
|
|
244
255
|
TFX_NO_CLAUDE_NATIVE="${TFX_NO_CLAUDE_NATIVE:-0}"
|
|
256
|
+
TFX_CODEX_TRANSPORT="${TFX_CODEX_TRANSPORT:-auto}"
|
|
257
|
+
TFX_WORKER_INDEX="${TFX_WORKER_INDEX:-}"
|
|
258
|
+
TFX_SEARCH_TOOL="${TFX_SEARCH_TOOL:-}"
|
|
245
259
|
case "$TFX_NO_CLAUDE_NATIVE" in
|
|
246
260
|
0|1) ;;
|
|
247
261
|
*)
|
|
@@ -249,6 +263,28 @@ case "$TFX_NO_CLAUDE_NATIVE" in
|
|
|
249
263
|
exit 1
|
|
250
264
|
;;
|
|
251
265
|
esac
|
|
266
|
+
case "$TFX_CODEX_TRANSPORT" in
|
|
267
|
+
auto|mcp|exec) ;;
|
|
268
|
+
*)
|
|
269
|
+
echo "ERROR: TFX_CODEX_TRANSPORT 값은 auto, mcp, exec 중 하나여야 합니다. (현재: $TFX_CODEX_TRANSPORT)" >&2
|
|
270
|
+
exit 1
|
|
271
|
+
;;
|
|
272
|
+
esac
|
|
273
|
+
case "$TFX_WORKER_INDEX" in
|
|
274
|
+
"") ;;
|
|
275
|
+
*[!0-9]*|0)
|
|
276
|
+
echo "ERROR: TFX_WORKER_INDEX 값은 1 이상의 정수여야 합니다. (현재: $TFX_WORKER_INDEX)" >&2
|
|
277
|
+
exit 1
|
|
278
|
+
;;
|
|
279
|
+
esac
|
|
280
|
+
case "$TFX_SEARCH_TOOL" in
|
|
281
|
+
""|brave-search|tavily|exa) ;;
|
|
282
|
+
*)
|
|
283
|
+
echo "ERROR: TFX_SEARCH_TOOL 값은 brave-search, tavily, exa 중 하나여야 합니다. (현재: $TFX_SEARCH_TOOL)" >&2
|
|
284
|
+
exit 1
|
|
285
|
+
;;
|
|
286
|
+
esac
|
|
287
|
+
CODEX_MCP_TRANSPORT_EXIT_CODE=70
|
|
252
288
|
|
|
253
289
|
apply_cli_mode() {
|
|
254
290
|
local codex_base="--dangerously-bypass-approvals-and-sandbox --skip-git-repo-check"
|
|
@@ -389,23 +425,60 @@ get_mcp_hint() {
|
|
|
389
425
|
|
|
390
426
|
has_server() { echo ",$servers," | grep -q ",$1,"; }
|
|
391
427
|
|
|
428
|
+
get_search_tool_order() {
|
|
429
|
+
local available=()
|
|
430
|
+
local ordered=()
|
|
431
|
+
local tool
|
|
432
|
+
|
|
433
|
+
for tool in brave-search tavily exa; do
|
|
434
|
+
has_server "$tool" && available+=("$tool")
|
|
435
|
+
done
|
|
436
|
+
|
|
437
|
+
if [[ ${#available[@]} -eq 0 ]]; then
|
|
438
|
+
return 0
|
|
439
|
+
fi
|
|
440
|
+
|
|
441
|
+
if [[ -n "$TFX_SEARCH_TOOL" ]]; then
|
|
442
|
+
for tool in "${available[@]}"; do
|
|
443
|
+
[[ "$tool" == "$TFX_SEARCH_TOOL" ]] && ordered+=("$tool")
|
|
444
|
+
done
|
|
445
|
+
for tool in "${available[@]}"; do
|
|
446
|
+
[[ "$tool" != "$TFX_SEARCH_TOOL" ]] && ordered+=("$tool")
|
|
447
|
+
done
|
|
448
|
+
elif [[ -n "$TFX_WORKER_INDEX" && ${#available[@]} -gt 1 ]]; then
|
|
449
|
+
local offset=$(( (TFX_WORKER_INDEX - 1) % ${#available[@]} ))
|
|
450
|
+
local i idx
|
|
451
|
+
for ((i=0; i<${#available[@]}; i++)); do
|
|
452
|
+
idx=$(( (offset + i) % ${#available[@]} ))
|
|
453
|
+
ordered+=("${available[$idx]}")
|
|
454
|
+
done
|
|
455
|
+
else
|
|
456
|
+
ordered=("${available[@]}")
|
|
457
|
+
fi
|
|
458
|
+
|
|
459
|
+
printf '%s\n' "${ordered[*]}"
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
local ordered_tools=()
|
|
463
|
+
read -r -a ordered_tools <<< "$(get_search_tool_order)"
|
|
464
|
+
local ordered_tools_csv=""
|
|
465
|
+
if [[ ${#ordered_tools[@]} -gt 0 ]]; then
|
|
466
|
+
ordered_tools_csv=$(printf '%s, ' "${ordered_tools[@]}")
|
|
467
|
+
ordered_tools_csv="${ordered_tools_csv%, }"
|
|
468
|
+
fi
|
|
469
|
+
|
|
392
470
|
local hint=""
|
|
393
471
|
case "$profile" in
|
|
394
472
|
implement)
|
|
395
473
|
has_server "context7" && hint+="context7으로 라이브러리 문서를 조회하세요. "
|
|
396
|
-
if
|
|
397
|
-
|
|
398
|
-
elif has_server "tavily"; then hint+="웹 검색은 tavily를 사용하세요. "
|
|
474
|
+
if [[ ${#ordered_tools[@]} -gt 0 ]]; then
|
|
475
|
+
hint+="웹 검색은 ${ordered_tools[0]}를 사용하세요. "
|
|
399
476
|
fi
|
|
400
|
-
hint+="검색 도구 실패 시 재시도하지 말고 다음 도구로 전환하세요."
|
|
477
|
+
hint+="검색 도구 실패 시 402, 429, 432, 433, quota 에러에서 재시도하지 말고 다음 도구로 전환하세요."
|
|
401
478
|
;;
|
|
402
479
|
analyze)
|
|
403
480
|
has_server "context7" && hint+="context7으로 관련 문서를 조회하세요. "
|
|
404
|
-
|
|
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 에러 시 즉시 다음 도구로 전환. "
|
|
481
|
+
[[ -n "$ordered_tools_csv" ]] && hint+="웹 검색 우선순위: ${ordered_tools_csv}. 402, 429, 432, 433, quota 에러 시 즉시 다음 도구로 전환. "
|
|
409
482
|
has_server "playwright" && hint+="모든 검색 실패 시 playwright로 직접 방문 (최대 3 URL). "
|
|
410
483
|
hint+="검색 깊이를 제한하고 결과를 빠르게 요약하세요."
|
|
411
484
|
;;
|
|
@@ -414,7 +487,9 @@ get_mcp_hint() {
|
|
|
414
487
|
;;
|
|
415
488
|
docs)
|
|
416
489
|
has_server "context7" && hint+="context7으로 공식 문서를 참조하세요. "
|
|
417
|
-
|
|
490
|
+
if [[ ${#ordered_tools[@]} -gt 0 ]]; then
|
|
491
|
+
hint+="추가 검색은 ${ordered_tools[0]}를 사용하세요. "
|
|
492
|
+
fi
|
|
418
493
|
hint+="검색 결과의 출처 URL을 함께 제시하세요."
|
|
419
494
|
;;
|
|
420
495
|
minimal|none) ;;
|
|
@@ -423,17 +498,267 @@ get_mcp_hint() {
|
|
|
423
498
|
}
|
|
424
499
|
|
|
425
500
|
# ── Gemini MCP 서버 선택적 로드 ──
|
|
426
|
-
|
|
501
|
+
get_gemini_mcp_servers() {
|
|
427
502
|
local profile="$1"
|
|
428
503
|
case "$profile" in
|
|
429
|
-
implement) echo "
|
|
430
|
-
analyze) echo "
|
|
431
|
-
review) echo "
|
|
432
|
-
docs) echo "
|
|
504
|
+
implement) echo "context7 brave-search" ;;
|
|
505
|
+
analyze) echo "context7 brave-search exa tavily" ;;
|
|
506
|
+
review) echo "sequential-thinking" ;;
|
|
507
|
+
docs) echo "context7 brave-search" ;;
|
|
433
508
|
*) echo "" ;;
|
|
434
509
|
esac
|
|
435
510
|
}
|
|
436
511
|
|
|
512
|
+
get_gemini_mcp_filter() {
|
|
513
|
+
local servers
|
|
514
|
+
servers=$(get_gemini_mcp_servers "$1")
|
|
515
|
+
[[ -z "$servers" ]] && return 0
|
|
516
|
+
echo "--allowed-mcp-server-names ${servers// /,}"
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
get_claude_model() {
|
|
520
|
+
case "$AGENT_TYPE" in
|
|
521
|
+
explore) echo "haiku" ;;
|
|
522
|
+
*) echo "sonnet" ;;
|
|
523
|
+
esac
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
emit_claude_native_metadata() {
|
|
527
|
+
local model
|
|
528
|
+
model=$(get_claude_model)
|
|
529
|
+
echo "ROUTE_TYPE=claude-native"
|
|
530
|
+
echo "AGENT=$AGENT_TYPE"
|
|
531
|
+
echo "MODEL=$model"
|
|
532
|
+
echo "RUN_MODE=$RUN_MODE"
|
|
533
|
+
echo "OPUS_OVERSIGHT=$OPUS_OVERSIGHT"
|
|
534
|
+
echo "TIMEOUT=$TIMEOUT_SEC"
|
|
535
|
+
echo "MCP_PROFILE=$MCP_PROFILE"
|
|
536
|
+
[[ -n "$ORIGINAL_AGENT" ]] && echo "ORIGINAL_AGENT=$ORIGINAL_AGENT"
|
|
537
|
+
echo "PROMPT=$PROMPT"
|
|
538
|
+
echo "--- Claude Task($model) 에이전트로 위임하세요 ---"
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
resolve_worker_runner_script() {
|
|
542
|
+
if [[ -n "${TFX_ROUTE_WORKER_RUNNER:-}" && -f "$TFX_ROUTE_WORKER_RUNNER" ]]; then
|
|
543
|
+
printf '%s\n' "$TFX_ROUTE_WORKER_RUNNER"
|
|
544
|
+
return 0
|
|
545
|
+
fi
|
|
546
|
+
|
|
547
|
+
local script_dir
|
|
548
|
+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
549
|
+
local candidate="$script_dir/tfx-route-worker.mjs"
|
|
550
|
+
[[ -f "$candidate" ]] || return 1
|
|
551
|
+
printf '%s\n' "$candidate"
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
run_stream_worker() {
|
|
555
|
+
local worker_type="$1"
|
|
556
|
+
local prompt="$2"
|
|
557
|
+
local use_tee_flag="$3"
|
|
558
|
+
shift 3
|
|
559
|
+
|
|
560
|
+
local runner_script
|
|
561
|
+
if ! runner_script=$(resolve_worker_runner_script); then
|
|
562
|
+
echo "[tfx-route] 경고: stream worker runner를 찾지 못했습니다." >&2
|
|
563
|
+
return 127
|
|
564
|
+
fi
|
|
565
|
+
|
|
566
|
+
if ! command -v "$NODE_BIN" &>/dev/null; then
|
|
567
|
+
echo "[tfx-route] 경고: node를 찾지 못해 stream worker를 실행할 수 없습니다." >&2
|
|
568
|
+
return 127
|
|
569
|
+
fi
|
|
570
|
+
|
|
571
|
+
local -a worker_cmd=(
|
|
572
|
+
"$NODE_BIN"
|
|
573
|
+
"$runner_script"
|
|
574
|
+
"--type" "$worker_type"
|
|
575
|
+
"--timeout-ms" "$((TIMEOUT_SEC * 1000))"
|
|
576
|
+
"--cwd" "$PWD"
|
|
577
|
+
"$@"
|
|
578
|
+
)
|
|
579
|
+
|
|
580
|
+
if [[ "$use_tee_flag" == "true" ]]; then
|
|
581
|
+
printf '%s' "$prompt" | timeout "$TIMEOUT_SEC" "${worker_cmd[@]}" 2>"$STDERR_LOG" | tee "$STDOUT_LOG"
|
|
582
|
+
else
|
|
583
|
+
printf '%s' "$prompt" | timeout "$TIMEOUT_SEC" "${worker_cmd[@]}" >"$STDOUT_LOG" 2>"$STDERR_LOG"
|
|
584
|
+
fi
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
run_legacy_gemini() {
|
|
588
|
+
local prompt="$1"
|
|
589
|
+
local use_tee_flag="$2"
|
|
590
|
+
local gemini_mcp_filter
|
|
591
|
+
gemini_mcp_filter=$(get_gemini_mcp_filter "$MCP_PROFILE")
|
|
592
|
+
local gemini_args="$CLI_ARGS"
|
|
593
|
+
|
|
594
|
+
if [[ -n "$gemini_mcp_filter" ]]; then
|
|
595
|
+
gemini_args="${CLI_ARGS/--prompt/$gemini_mcp_filter --prompt}"
|
|
596
|
+
echo "[tfx-route] Gemini MCP 필터: $gemini_mcp_filter" >&2
|
|
597
|
+
fi
|
|
598
|
+
|
|
599
|
+
if [[ "$use_tee_flag" == "true" ]]; then
|
|
600
|
+
timeout "$TIMEOUT_SEC" $CLI_CMD $gemini_args "$prompt" 2>"$STDERR_LOG" | tee "$STDOUT_LOG" &
|
|
601
|
+
else
|
|
602
|
+
timeout "$TIMEOUT_SEC" $CLI_CMD $gemini_args "$prompt" >"$STDOUT_LOG" 2>"$STDERR_LOG" &
|
|
603
|
+
fi
|
|
604
|
+
local pid=$!
|
|
605
|
+
|
|
606
|
+
local health_ok=true
|
|
607
|
+
local intervals=(1 2 3 5 8)
|
|
608
|
+
for wait_sec in "${intervals[@]}"; do
|
|
609
|
+
sleep "$wait_sec"
|
|
610
|
+
if [[ -s "$STDOUT_LOG" ]] || [[ -s "$STDERR_LOG" ]]; then
|
|
611
|
+
break
|
|
612
|
+
fi
|
|
613
|
+
if ! kill -0 "$pid" 2>/dev/null; then
|
|
614
|
+
health_ok=false
|
|
615
|
+
echo "[tfx-route] Gemini: 출력 없이 프로세스 종료 (${wait_sec}초 체크)" >&2
|
|
616
|
+
break
|
|
617
|
+
fi
|
|
618
|
+
done
|
|
619
|
+
|
|
620
|
+
local exit_code_local=0
|
|
621
|
+
if [[ "$health_ok" == "false" ]]; then
|
|
622
|
+
wait "$pid" 2>/dev/null
|
|
623
|
+
echo "[tfx-route] Gemini crash 감지, 재시도 중..." >&2
|
|
624
|
+
if [[ "$use_tee_flag" == "true" ]]; then
|
|
625
|
+
timeout "$TIMEOUT_SEC" $CLI_CMD $gemini_args "$prompt" 2>"$STDERR_LOG" | tee "$STDOUT_LOG" &
|
|
626
|
+
else
|
|
627
|
+
timeout "$TIMEOUT_SEC" $CLI_CMD $gemini_args "$prompt" >"$STDOUT_LOG" 2>"$STDERR_LOG" &
|
|
628
|
+
fi
|
|
629
|
+
pid=$!
|
|
630
|
+
fi
|
|
631
|
+
|
|
632
|
+
wait "$pid" || exit_code_local=$?
|
|
633
|
+
return "$exit_code_local"
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
resolve_codex_mcp_script() {
|
|
637
|
+
if [[ -n "${TFX_CODEX_MCP_SCRIPT:-}" && -f "$TFX_CODEX_MCP_SCRIPT" ]]; then
|
|
638
|
+
printf '%s\n' "$TFX_CODEX_MCP_SCRIPT"
|
|
639
|
+
return 0
|
|
640
|
+
fi
|
|
641
|
+
|
|
642
|
+
local script_dir
|
|
643
|
+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
644
|
+
local candidates=(
|
|
645
|
+
"$script_dir/hub/workers/codex-mcp.mjs"
|
|
646
|
+
"$script_dir/../hub/workers/codex-mcp.mjs"
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
local candidate
|
|
650
|
+
for candidate in "${candidates[@]}"; do
|
|
651
|
+
if [[ -f "$candidate" ]]; then
|
|
652
|
+
printf '%s\n' "$candidate"
|
|
653
|
+
return 0
|
|
654
|
+
fi
|
|
655
|
+
done
|
|
656
|
+
|
|
657
|
+
return 1
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
run_codex_exec() {
|
|
661
|
+
local prompt="$1"
|
|
662
|
+
local use_tee_flag="$2"
|
|
663
|
+
local exit_code_local=0
|
|
664
|
+
|
|
665
|
+
if [[ "$use_tee_flag" == "true" ]]; then
|
|
666
|
+
timeout "$TIMEOUT_SEC" $CLI_CMD $CLI_ARGS "$prompt" 2>"$STDERR_LOG" | tee "$STDOUT_LOG" || exit_code_local=$?
|
|
667
|
+
else
|
|
668
|
+
timeout "$TIMEOUT_SEC" $CLI_CMD $CLI_ARGS "$prompt" >"$STDOUT_LOG" 2>"$STDERR_LOG" || exit_code_local=$?
|
|
669
|
+
fi
|
|
670
|
+
|
|
671
|
+
if [[ ! -s "$STDOUT_LOG" && -s "$STDERR_LOG" ]]; then
|
|
672
|
+
# stderr에서 마지막 "codex" 마커 이후의 텍스트를 stdout으로 복구
|
|
673
|
+
# 1차: "codex" 마커 기반 (Windows \r 제거 후 매칭)
|
|
674
|
+
sed 's/\r$//' "$STDERR_LOG" \
|
|
675
|
+
| awk '/^codex$/{found=NR;content=""} found && NR>found{content=content RS $0} END{if(content) print substr(content,2)}' \
|
|
676
|
+
> "$STDOUT_LOG"
|
|
677
|
+
|
|
678
|
+
# 2차: 마커 없을 때 node fallback (MCP/헤더/sandbox 로그 제외, 응답 부분만 추출)
|
|
679
|
+
if [[ ! -s "$STDOUT_LOG" ]]; then
|
|
680
|
+
node -e '
|
|
681
|
+
const fs=require("fs"),lines=fs.readFileSync(process.argv[1],"utf-8").split(/\r?\n/);
|
|
682
|
+
const skip=/^(mcp[: ]|OpenAI Codex|--------|workdir:|model:|provider:|approval:|sandbox:|reasoning|session id:|user$|tokens used|EXIT:|exec$|"[A-Z]:|succeeded in |\s*$)/;
|
|
683
|
+
const out=lines.filter(l=>!skip.test(l));
|
|
684
|
+
if(out.length) fs.writeFileSync(process.argv[2],out.join("\n"));
|
|
685
|
+
' -- "$STDERR_LOG" "$STDOUT_LOG" 2>/dev/null || true
|
|
686
|
+
fi
|
|
687
|
+
|
|
688
|
+
if [[ -s "$STDOUT_LOG" ]]; then
|
|
689
|
+
echo "[tfx-route] 경고: codex stdout 비어있음, stderr에서 응답 복구 ($(wc -c < "$STDOUT_LOG") bytes)" >&2
|
|
690
|
+
else
|
|
691
|
+
echo "[tfx-route] 경고: codex stdout 비어있음, stderr 복구도 실패" >&2
|
|
692
|
+
fi
|
|
693
|
+
fi
|
|
694
|
+
|
|
695
|
+
return "$exit_code_local"
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
run_codex_mcp() {
|
|
699
|
+
local prompt="$1"
|
|
700
|
+
local use_tee_flag="$2"
|
|
701
|
+
local mcp_script node_bin
|
|
702
|
+
local exit_code_local=0
|
|
703
|
+
|
|
704
|
+
if ! mcp_script=$(resolve_codex_mcp_script); then
|
|
705
|
+
echo "[tfx-route] 경고: Codex MCP 래퍼를 찾지 못했습니다." >&2
|
|
706
|
+
return "$CODEX_MCP_TRANSPORT_EXIT_CODE"
|
|
707
|
+
fi
|
|
708
|
+
|
|
709
|
+
node_bin="${NODE_BIN:-$(command -v node 2>/dev/null || echo node)}"
|
|
710
|
+
if ! command -v "$node_bin" &>/dev/null; then
|
|
711
|
+
echo "[tfx-route] 경고: node를 찾지 못해 Codex MCP 경로를 사용할 수 없습니다." >&2
|
|
712
|
+
return "$CODEX_MCP_TRANSPORT_EXIT_CODE"
|
|
713
|
+
fi
|
|
714
|
+
|
|
715
|
+
local -a mcp_args=(
|
|
716
|
+
"$mcp_script"
|
|
717
|
+
"--prompt" "$prompt"
|
|
718
|
+
"--cwd" "$PWD"
|
|
719
|
+
"--profile" "$CLI_EFFORT"
|
|
720
|
+
"--approval-policy" "never"
|
|
721
|
+
"--sandbox" "danger-full-access"
|
|
722
|
+
"--timeout-ms" "$((TIMEOUT_SEC * 1000))"
|
|
723
|
+
"--codex-command" "$CODEX_BIN"
|
|
724
|
+
)
|
|
725
|
+
|
|
726
|
+
case "$AGENT_TYPE" in
|
|
727
|
+
code-reviewer)
|
|
728
|
+
mcp_args+=(
|
|
729
|
+
"--developer-instructions"
|
|
730
|
+
"코드 리뷰 모드로 동작하라. 버그, 리스크, 회귀, 테스트 누락을 우선 식별하라."
|
|
731
|
+
)
|
|
732
|
+
;;
|
|
733
|
+
security-reviewer)
|
|
734
|
+
mcp_args+=(
|
|
735
|
+
"--developer-instructions"
|
|
736
|
+
"보안 리뷰 모드로 동작하라. 취약점, 권한 경계, 비밀정보 노출 가능성을 우선 식별하라."
|
|
737
|
+
)
|
|
738
|
+
;;
|
|
739
|
+
quality-reviewer)
|
|
740
|
+
mcp_args+=(
|
|
741
|
+
"--developer-instructions"
|
|
742
|
+
"품질 리뷰 모드로 동작하라. 로직 결함, 유지보수성 저하, 테스트 누락을 우선 식별하라."
|
|
743
|
+
)
|
|
744
|
+
;;
|
|
745
|
+
esac
|
|
746
|
+
|
|
747
|
+
if [[ "$use_tee_flag" == "true" ]]; then
|
|
748
|
+
timeout "$TIMEOUT_SEC" "$node_bin" "${mcp_args[@]}" 2>"$STDERR_LOG" | tee "$STDOUT_LOG" || exit_code_local=$?
|
|
749
|
+
else
|
|
750
|
+
timeout "$TIMEOUT_SEC" "$node_bin" "${mcp_args[@]}" >"$STDOUT_LOG" 2>"$STDERR_LOG" || exit_code_local=$?
|
|
751
|
+
fi
|
|
752
|
+
|
|
753
|
+
# 모듈 로드 실패(의존성 누락) → MCP transport exit code로 변환하여 fallback 트리거
|
|
754
|
+
if [[ "$exit_code_local" -ne 0 && "$exit_code_local" -ne 124 ]] && grep -q 'ERR_MODULE_NOT_FOUND' "$STDERR_LOG" 2>/dev/null; then
|
|
755
|
+
echo "[tfx-route] Codex MCP 모듈 로드 실패 — fallback 가능 exit code로 변환" >&2
|
|
756
|
+
return "$CODEX_MCP_TRANSPORT_EXIT_CODE"
|
|
757
|
+
fi
|
|
758
|
+
|
|
759
|
+
return "$exit_code_local"
|
|
760
|
+
}
|
|
761
|
+
|
|
437
762
|
# ── 메인 실행 ──
|
|
438
763
|
main() {
|
|
439
764
|
# 종료 시 per-process 에이전트 파일 자동 삭제
|
|
@@ -447,11 +772,30 @@ main() {
|
|
|
447
772
|
case "$CLI_CMD" in
|
|
448
773
|
codex) CLI_CMD="$CODEX_BIN" ;;
|
|
449
774
|
gemini) CLI_CMD="$GEMINI_BIN" ;;
|
|
775
|
+
claude) CLI_CMD="$CLAUDE_BIN" ;;
|
|
776
|
+
esac
|
|
777
|
+
|
|
778
|
+
# 타임아웃 결정 (에이전트별 최소값 보장)
|
|
779
|
+
local MIN_TIMEOUT
|
|
780
|
+
case "$AGENT_TYPE" in
|
|
781
|
+
deep-executor|architect|planner|critic|analyst) MIN_TIMEOUT=900 ;;
|
|
782
|
+
document-specialist|scientist|scientist-deep) MIN_TIMEOUT=900 ;;
|
|
783
|
+
code-reviewer|security-reviewer|quality-reviewer) MIN_TIMEOUT=600 ;;
|
|
784
|
+
executor|debugger) MIN_TIMEOUT=300 ;;
|
|
785
|
+
*) MIN_TIMEOUT=120 ;;
|
|
450
786
|
esac
|
|
451
787
|
|
|
452
|
-
# 타임아웃 결정
|
|
453
788
|
if [[ -n "$USER_TIMEOUT" ]]; then
|
|
454
|
-
|
|
789
|
+
if ! [[ "$USER_TIMEOUT" =~ ^[1-9][0-9]*$ ]]; then
|
|
790
|
+
echo "[tfx-route] 경고: 유효하지 않은 타임아웃 값 ($USER_TIMEOUT), 기본값 사용" >&2
|
|
791
|
+
USER_TIMEOUT=""
|
|
792
|
+
TIMEOUT_SEC="$DEFAULT_TIMEOUT"
|
|
793
|
+
elif [[ "$USER_TIMEOUT" -lt "$MIN_TIMEOUT" ]]; then
|
|
794
|
+
echo "[tfx-route] 경고: 타임아웃 ${USER_TIMEOUT}s < 최소 ${MIN_TIMEOUT}s ($AGENT_TYPE), 최소값 적용" >&2
|
|
795
|
+
TIMEOUT_SEC="$MIN_TIMEOUT"
|
|
796
|
+
else
|
|
797
|
+
TIMEOUT_SEC="$USER_TIMEOUT"
|
|
798
|
+
fi
|
|
455
799
|
else
|
|
456
800
|
TIMEOUT_SEC="$DEFAULT_TIMEOUT"
|
|
457
801
|
fi
|
|
@@ -467,22 +811,20 @@ ${ctx_content}
|
|
|
467
811
|
</prior_context>"
|
|
468
812
|
fi
|
|
469
813
|
|
|
814
|
+
# Claude native는 팀 비-TTY 환경에서 subprocess wrapper를 우선 시도
|
|
815
|
+
if [[ "$CLI_TYPE" == "claude-native" && -n "$TFX_TEAM_NAME" ]]; then
|
|
816
|
+
if { [[ ! -t 0 ]] || [[ ! -t 1 ]]; } && command -v "$CLAUDE_BIN" &>/dev/null && resolve_worker_runner_script >/dev/null 2>&1; then
|
|
817
|
+
CLI_TYPE="claude"
|
|
818
|
+
CLI_CMD="$CLAUDE_BIN"
|
|
819
|
+
echo "[tfx-route] non-tty 팀 환경: claude-native -> claude stream wrapper 전환" >&2
|
|
820
|
+
else
|
|
821
|
+
echo "[tfx-route] claude stream wrapper 미사용: native metadata 유지" >&2
|
|
822
|
+
fi
|
|
823
|
+
fi
|
|
824
|
+
|
|
470
825
|
# Claude 네이티브 에이전트는 이 스크립트로 처리 불가 → 메타데이터만 출력
|
|
471
826
|
if [[ "$CLI_TYPE" == "claude-native" ]]; then
|
|
472
|
-
|
|
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) 에이전트로 위임하세요 ---"
|
|
827
|
+
emit_claude_native_metadata
|
|
486
828
|
exit 0
|
|
487
829
|
fi
|
|
488
830
|
|
|
@@ -491,10 +833,17 @@ ${ctx_content}
|
|
|
491
833
|
mcp_hint=$(get_mcp_hint "$MCP_PROFILE" "$AGENT_TYPE")
|
|
492
834
|
local FULL_PROMPT="$PROMPT"
|
|
493
835
|
[[ -n "$mcp_hint" ]] && FULL_PROMPT="${PROMPT}. ${mcp_hint}"
|
|
836
|
+
local codex_transport_effective="n/a"
|
|
494
837
|
|
|
495
838
|
# 메타정보 (stderr)
|
|
496
839
|
echo "[tfx-route] v${VERSION} type=$CLI_TYPE agent=$AGENT_TYPE effort=$CLI_EFFORT mode=$RUN_MODE timeout=${TIMEOUT_SEC}s" >&2
|
|
497
840
|
echo "[tfx-route] opus_oversight=$OPUS_OVERSIGHT mcp_profile=$MCP_PROFILE" >&2
|
|
841
|
+
if [[ -n "$TFX_WORKER_INDEX" || -n "$TFX_SEARCH_TOOL" ]]; then
|
|
842
|
+
echo "[tfx-route] worker_index=${TFX_WORKER_INDEX:-auto} search_tool=${TFX_SEARCH_TOOL:-auto}" >&2
|
|
843
|
+
fi
|
|
844
|
+
if [[ "$CLI_TYPE" == "codex" ]]; then
|
|
845
|
+
echo "[tfx-route] codex_transport_request=$TFX_CODEX_TRANSPORT" >&2
|
|
846
|
+
fi
|
|
498
847
|
[[ -n "$TFX_TEAM_NAME" ]] && echo "[tfx-route] team=$TFX_TEAM_NAME task=$TFX_TEAM_TASK_ID agent=$TFX_TEAM_AGENT_NAME" >&2
|
|
499
848
|
|
|
500
849
|
# Per-process 에이전트 등록
|
|
@@ -508,50 +857,94 @@ ${ctx_content}
|
|
|
508
857
|
local start_time
|
|
509
858
|
start_time=$(date +%s)
|
|
510
859
|
|
|
860
|
+
# tee 활성화 조건: 팀 모드 + 실제 터미널(TTY/tmux)
|
|
861
|
+
# Agent 래퍼 안에서는 가상 stdout 캡처로 tee 출력이 사용자에게 안 보임 → 파일 전용
|
|
862
|
+
# 실시간 모니터링은 Shift+Down으로 워커 pane 전환 권장
|
|
863
|
+
local use_tee=false
|
|
864
|
+
if [[ -n "$TFX_TEAM_NAME" ]]; then
|
|
865
|
+
if [[ -t 1 ]] || [[ -n "${TMUX:-}" ]]; then
|
|
866
|
+
use_tee=true
|
|
867
|
+
fi
|
|
868
|
+
fi
|
|
869
|
+
|
|
511
870
|
if [[ "$CLI_TYPE" == "codex" ]]; then
|
|
512
|
-
|
|
513
|
-
|
|
871
|
+
codex_transport_effective="exec"
|
|
872
|
+
if [[ "$TFX_CODEX_TRANSPORT" != "exec" ]]; then
|
|
873
|
+
run_codex_mcp "$FULL_PROMPT" "$use_tee" || exit_code=$?
|
|
874
|
+
if [[ "$exit_code" -eq 0 ]]; then
|
|
875
|
+
codex_transport_effective="mcp"
|
|
876
|
+
elif [[ "$exit_code" -eq "$CODEX_MCP_TRANSPORT_EXIT_CODE" && "$TFX_CODEX_TRANSPORT" == "auto" ]]; then
|
|
877
|
+
echo "[tfx-route] Codex MCP bootstrap 실패(exit=${exit_code}). legacy exec 경로로 fallback합니다." >&2
|
|
878
|
+
: > "$STDOUT_LOG"
|
|
879
|
+
: > "$STDERR_LOG"
|
|
880
|
+
exit_code=0
|
|
881
|
+
run_codex_exec "$FULL_PROMPT" "$use_tee" || exit_code=$?
|
|
882
|
+
codex_transport_effective="exec-fallback"
|
|
883
|
+
else
|
|
884
|
+
codex_transport_effective="mcp"
|
|
885
|
+
fi
|
|
886
|
+
else
|
|
887
|
+
run_codex_exec "$FULL_PROMPT" "$use_tee" || exit_code=$?
|
|
888
|
+
codex_transport_effective="exec"
|
|
889
|
+
fi
|
|
890
|
+
echo "[tfx-route] codex_transport_effective=$codex_transport_effective" >&2
|
|
514
891
|
|
|
515
892
|
elif [[ "$CLI_TYPE" == "gemini" ]]; then
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
893
|
+
local gemini_model
|
|
894
|
+
gemini_model=$(awk '{
|
|
895
|
+
for (i = 1; i <= NF; i++) {
|
|
896
|
+
if ($i == "-m" || $i == "--model") {
|
|
897
|
+
print $(i + 1)
|
|
898
|
+
exit
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
}' <<< "$CLI_ARGS")
|
|
902
|
+
local gemini_servers
|
|
903
|
+
gemini_servers=$(get_gemini_mcp_servers "$MCP_PROFILE")
|
|
904
|
+
local -a gemini_worker_args=(
|
|
905
|
+
"--command" "$CLI_CMD"
|
|
906
|
+
"--command-args-json" "$GEMINI_BIN_ARGS_JSON"
|
|
907
|
+
"--model" "$gemini_model"
|
|
908
|
+
"--approval-mode" "yolo"
|
|
909
|
+
)
|
|
910
|
+
|
|
911
|
+
if [[ -n "$gemini_servers" ]]; then
|
|
912
|
+
echo "[tfx-route] Gemini MCP 서버: ${gemini_servers}" >&2
|
|
913
|
+
local server_name
|
|
914
|
+
for server_name in $gemini_servers; do
|
|
915
|
+
gemini_worker_args+=("--allowed-mcp-server-name" "$server_name")
|
|
916
|
+
done
|
|
523
917
|
fi
|
|
524
918
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
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
|
|
919
|
+
run_stream_worker "gemini" "$FULL_PROMPT" "$use_tee" "${gemini_worker_args[@]}" || exit_code=$?
|
|
920
|
+
if [[ "$exit_code" -ne 0 && "$exit_code" -ne 124 ]]; then
|
|
921
|
+
echo "[tfx-route] Gemini stream wrapper 실패(exit=${exit_code}). legacy CLI 경로로 fallback합니다." >&2
|
|
922
|
+
: > "$STDOUT_LOG"
|
|
923
|
+
: > "$STDERR_LOG"
|
|
924
|
+
exit_code=0
|
|
925
|
+
run_legacy_gemini "$FULL_PROMPT" "$use_tee" || exit_code=$?
|
|
926
|
+
fi
|
|
544
927
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
928
|
+
elif [[ "$CLI_TYPE" == "claude" ]]; then
|
|
929
|
+
local claude_model
|
|
930
|
+
claude_model=$(get_claude_model)
|
|
931
|
+
local -a claude_worker_args=(
|
|
932
|
+
"--command" "$CLI_CMD"
|
|
933
|
+
"--command-args-json" "$CLAUDE_BIN_ARGS_JSON"
|
|
934
|
+
"--model" "$claude_model"
|
|
935
|
+
"--permission-mode" "bypassPermissions"
|
|
936
|
+
"--allow-dangerously-skip-permissions"
|
|
937
|
+
)
|
|
938
|
+
|
|
939
|
+
run_stream_worker "claude" "$FULL_PROMPT" "$use_tee" "${claude_worker_args[@]}" || exit_code=$?
|
|
940
|
+
if [[ "$exit_code" -ne 0 && "$exit_code" -ne 124 ]]; then
|
|
941
|
+
echo "[tfx-route] Claude stream wrapper 실패(exit=${exit_code}). native metadata로 fallback합니다." >&2
|
|
942
|
+
cat > "$STDOUT_LOG" <<EOF
|
|
943
|
+
$(emit_claude_native_metadata)
|
|
944
|
+
EOF
|
|
945
|
+
: > "$STDERR_LOG"
|
|
946
|
+
exit_code=0
|
|
947
|
+
CLI_TYPE="claude-native"
|
|
555
948
|
fi
|
|
556
949
|
fi
|
|
557
950
|
|
|
@@ -564,9 +957,9 @@ ${ctx_content}
|
|
|
564
957
|
if [[ "$exit_code" -eq 0 ]]; then
|
|
565
958
|
local output_preview
|
|
566
959
|
output_preview=$(head -c 2048 "$STDOUT_LOG" 2>/dev/null || echo "출력 없음")
|
|
567
|
-
team_complete_task "
|
|
960
|
+
team_complete_task "success" "$output_preview"
|
|
568
961
|
elif [[ "$exit_code" -eq 124 ]]; then
|
|
569
|
-
team_complete_task "
|
|
962
|
+
team_complete_task "timeout" "타임아웃 (${TIMEOUT_SEC}초)"
|
|
570
963
|
else
|
|
571
964
|
local err_preview
|
|
572
965
|
err_preview=$(tail -c 1024 "$STDERR_LOG" 2>/dev/null || echo "에러 정보 없음")
|
|
@@ -591,7 +984,8 @@ ${ctx_content}
|
|
|
591
984
|
--mcp-profile "$MCP_PROFILE" \
|
|
592
985
|
--stderr-log "$STDERR_LOG" \
|
|
593
986
|
--stdout-log "$STDOUT_LOG" \
|
|
594
|
-
--max-bytes "$MAX_STDOUT_BYTES"
|
|
987
|
+
--max-bytes "$MAX_STDOUT_BYTES" \
|
|
988
|
+
--tee-active "$use_tee"
|
|
595
989
|
else
|
|
596
990
|
# post.mjs 없으면 기본 출력 (fallback)
|
|
597
991
|
echo "=== TFX-ROUTE RESULT ==="
|