triflux 3.3.0-dev.3 → 3.3.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.
- package/hub/assign-callbacks.mjs +136 -0
- package/hub/bridge.mjs +283 -97
- package/hub/pipe.mjs +81 -0
- package/hub/server.mjs +56 -53
- package/hub/store.mjs +36 -2
- package/hub/team/cli-team-status.mjs +17 -3
- package/hub/team/native-supervisor.mjs +62 -22
- package/hub/team/native.mjs +266 -200
- package/hub/workers/delegator-mcp.mjs +285 -140
- package/package.json +60 -60
- package/scripts/lib/mcp-filter.mjs +637 -0
- package/scripts/lib/mcp-server-catalog.mjs +118 -0
- package/scripts/mcp-check.mjs +126 -88
- package/scripts/test-tfx-route-no-claude-native.mjs +10 -2
- package/scripts/tfx-route.sh +434 -179
package/scripts/tfx-route.sh
CHANGED
|
@@ -48,7 +48,8 @@ TFX_TEAM_NAME="${TFX_TEAM_NAME:-}"
|
|
|
48
48
|
TFX_TEAM_TASK_ID="${TFX_TEAM_TASK_ID:-}"
|
|
49
49
|
TFX_TEAM_AGENT_NAME="${TFX_TEAM_AGENT_NAME:-${AGENT_TYPE}-worker-$$}"
|
|
50
50
|
TFX_TEAM_LEAD_NAME="${TFX_TEAM_LEAD_NAME:-team-lead}"
|
|
51
|
-
|
|
51
|
+
TFX_HUB_PIPE="${TFX_HUB_PIPE:-}"
|
|
52
|
+
TFX_HUB_URL="${TFX_HUB_URL:-http://127.0.0.1:27888}" # bridge.mjs HTTP fallback hint
|
|
52
53
|
|
|
53
54
|
# fallback 시 원래 에이전트 정보 보존
|
|
54
55
|
ORIGINAL_AGENT=""
|
|
@@ -65,45 +66,228 @@ deregister_agent() {
|
|
|
65
66
|
rm -f "${TFX_TMP}/tfx-agent-$$.json" 2>/dev/null || true
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
normalize_script_path() {
|
|
70
|
+
local path="${1:-}"
|
|
71
|
+
if [[ -z "$path" ]]; then
|
|
72
|
+
return 0
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
if command -v cygpath &>/dev/null; then
|
|
76
|
+
case "$path" in
|
|
77
|
+
[A-Za-z]:\\*|[A-Za-z]:/*)
|
|
78
|
+
cygpath -u "$path"
|
|
79
|
+
return 0
|
|
80
|
+
;;
|
|
81
|
+
esac
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
printf '%s\n' "$path"
|
|
85
|
+
}
|
|
86
|
+
|
|
68
87
|
# ── 팀 Hub Bridge 통신 ──
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
88
|
+
resolve_bridge_script() {
|
|
89
|
+
if [[ -n "${TFX_BRIDGE_SCRIPT:-}" && -f "$TFX_BRIDGE_SCRIPT" ]]; then
|
|
90
|
+
printf '%s\n' "$TFX_BRIDGE_SCRIPT"
|
|
91
|
+
return 0
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
local script_dir
|
|
95
|
+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
96
|
+
local candidates=(
|
|
97
|
+
"$script_dir/../hub/bridge.mjs"
|
|
98
|
+
"$script_dir/hub/bridge.mjs"
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
local candidate
|
|
102
|
+
for candidate in "${candidates[@]}"; do
|
|
103
|
+
if [[ -f "$candidate" ]]; then
|
|
104
|
+
printf '%s\n' "$candidate"
|
|
105
|
+
return 0
|
|
106
|
+
fi
|
|
107
|
+
done
|
|
108
|
+
|
|
109
|
+
return 1
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
bridge_cli() {
|
|
113
|
+
if ! command -v "$NODE_BIN" &>/dev/null; then
|
|
114
|
+
return 127
|
|
115
|
+
fi
|
|
116
|
+
|
|
117
|
+
local bridge_script
|
|
118
|
+
if ! bridge_script=$(resolve_bridge_script); then
|
|
119
|
+
return 127
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
TFX_HUB_PIPE="$TFX_HUB_PIPE" TFX_HUB_URL="$TFX_HUB_URL" TFX_HUB_TOKEN="${TFX_HUB_TOKEN:-}" \
|
|
123
|
+
"$NODE_BIN" "$bridge_script" "$@" 2>/dev/null
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
bridge_json_get() {
|
|
127
|
+
local json="${1:-}"
|
|
128
|
+
local path="${2:-}"
|
|
129
|
+
[[ -z "$json" || -z "$path" ]] && return 1
|
|
130
|
+
|
|
131
|
+
"$NODE_BIN" -e '
|
|
132
|
+
const data = JSON.parse(process.argv[1] || "{}");
|
|
133
|
+
const keys = String(process.argv[2] || "").split(".").filter(Boolean);
|
|
134
|
+
let value = data;
|
|
135
|
+
for (const key of keys) value = value?.[key];
|
|
136
|
+
if (value === undefined || value === null) process.exit(1);
|
|
137
|
+
process.stdout.write(typeof value === "object" ? JSON.stringify(value) : String(value));
|
|
138
|
+
' -- "$json" "$path" 2>/dev/null
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
bridge_json_stringify() {
|
|
142
|
+
local mode="${1:-}"
|
|
143
|
+
shift || true
|
|
144
|
+
|
|
145
|
+
case "$mode" in
|
|
146
|
+
metadata-patch)
|
|
147
|
+
"$NODE_BIN" -e '
|
|
148
|
+
process.stdout.write(JSON.stringify({
|
|
149
|
+
result: process.argv[1] || "",
|
|
150
|
+
summary: process.argv[2] || "",
|
|
151
|
+
}));
|
|
152
|
+
' -- "${1:-}" "${2:-}"
|
|
153
|
+
;;
|
|
154
|
+
task-result)
|
|
155
|
+
"$NODE_BIN" -e '
|
|
156
|
+
process.stdout.write(JSON.stringify({
|
|
157
|
+
task_id: process.argv[1] || "",
|
|
158
|
+
result: process.argv[2] || "",
|
|
159
|
+
}));
|
|
160
|
+
' -- "${1:-}" "${2:-}"
|
|
161
|
+
;;
|
|
162
|
+
*)
|
|
163
|
+
return 1
|
|
164
|
+
;;
|
|
165
|
+
esac
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
team_send_message() {
|
|
169
|
+
local text="${1:-}"
|
|
170
|
+
local summary="${2:-}"
|
|
171
|
+
[[ -z "$TFX_TEAM_NAME" || -z "$text" ]] && return 0
|
|
172
|
+
|
|
173
|
+
if ! bridge_cli_with_restart "팀 메시지 전송" "Hub 재시작 후 팀 메시지 전송 성공." \
|
|
174
|
+
team-send-message \
|
|
175
|
+
--team "$TFX_TEAM_NAME" \
|
|
176
|
+
--from "$TFX_TEAM_AGENT_NAME" \
|
|
177
|
+
--to "$TFX_TEAM_LEAD_NAME" \
|
|
178
|
+
--text "$text" \
|
|
179
|
+
--summary "${summary:-status update}"; then
|
|
180
|
+
echo "[tfx-route] 경고: 팀 메시지 전송 실패 (team=$TFX_TEAM_NAME, to=$TFX_TEAM_LEAD_NAME)" >&2
|
|
181
|
+
return 0
|
|
182
|
+
fi
|
|
183
|
+
|
|
184
|
+
return 0
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
# ── Hub 자동 재시작 (슬립 복귀 등으로 Hub 종료 시) ──
|
|
188
|
+
try_restart_hub() {
|
|
189
|
+
local hub_server script_dir hub_port
|
|
190
|
+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
191
|
+
hub_server="$script_dir/../hub/server.mjs"
|
|
192
|
+
|
|
193
|
+
if [[ ! -f "$hub_server" ]]; then
|
|
194
|
+
echo "[tfx-route] Hub 서버 스크립트 미발견: $hub_server" >&2
|
|
195
|
+
return 1
|
|
76
196
|
fi
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
197
|
+
|
|
198
|
+
# TFX_HUB_URL에서 포트 추출 (기본 27888)
|
|
199
|
+
hub_port="${TFX_HUB_URL##*:}"
|
|
200
|
+
hub_port="${hub_port%%/*}"
|
|
201
|
+
[[ -z "$hub_port" || "$hub_port" == "$TFX_HUB_URL" ]] && hub_port=27888
|
|
202
|
+
|
|
203
|
+
echo "[tfx-route] Hub 미응답 — 자동 재시작 시도 (port=$hub_port)..." >&2
|
|
204
|
+
TFX_HUB_PORT="$hub_port" "$NODE_BIN" "$hub_server" &>/dev/null &
|
|
205
|
+
local hub_pid=$!
|
|
206
|
+
|
|
207
|
+
# 최대 4초 대기 (0.5초 간격)
|
|
208
|
+
local i
|
|
209
|
+
for i in 1 2 3 4 5 6 7 8; do
|
|
210
|
+
sleep 0.5
|
|
211
|
+
if curl -sf "${TFX_HUB_URL}/status" >/dev/null 2>&1; then
|
|
212
|
+
echo "[tfx-route] Hub 재시작 성공 (pid=$hub_pid)" >&2
|
|
213
|
+
return 0
|
|
214
|
+
fi
|
|
215
|
+
done
|
|
216
|
+
|
|
217
|
+
echo "[tfx-route] Hub 재시작 실패 — claim 없이 계속 실행" >&2
|
|
218
|
+
return 1
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
bridge_cli_with_restart() {
|
|
222
|
+
local action_label="${1:-bridge 호출}"
|
|
223
|
+
local success_message="${2:-}"
|
|
224
|
+
shift 2 || true
|
|
225
|
+
|
|
226
|
+
if bridge_cli "$@" >/dev/null 2>&1; then
|
|
227
|
+
return 0
|
|
228
|
+
fi
|
|
229
|
+
|
|
230
|
+
if ! try_restart_hub; then
|
|
231
|
+
return 1
|
|
232
|
+
fi
|
|
233
|
+
|
|
234
|
+
if bridge_cli "$@" >/dev/null 2>&1; then
|
|
235
|
+
[[ -n "$success_message" ]] && echo "[tfx-route] ${success_message}" >&2
|
|
236
|
+
return 0
|
|
237
|
+
fi
|
|
238
|
+
|
|
239
|
+
echo "[tfx-route] 경고: Hub 재시작 후 ${action_label} 재시도 실패." >&2
|
|
240
|
+
return 1
|
|
84
241
|
}
|
|
85
242
|
|
|
86
243
|
team_claim_task() {
|
|
87
244
|
[[ -z "$TFX_TEAM_NAME" || -z "$TFX_TEAM_TASK_ID" ]] && return 0
|
|
88
|
-
local
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
245
|
+
local response ok error_code error_message owner_before status_before
|
|
246
|
+
response=$(bridge_cli team-task-update \
|
|
247
|
+
--team "$TFX_TEAM_NAME" \
|
|
248
|
+
--task-id "$TFX_TEAM_TASK_ID" \
|
|
249
|
+
--claim \
|
|
250
|
+
--owner "$TFX_TEAM_AGENT_NAME" \
|
|
251
|
+
--status in_progress || true)
|
|
252
|
+
|
|
253
|
+
ok=$(bridge_json_get "$response" "ok" || true)
|
|
254
|
+
error_code=$(bridge_json_get "$response" "error.code" || true)
|
|
255
|
+
error_message=$(bridge_json_get "$response" "error.message" || true)
|
|
256
|
+
owner_before=$(bridge_json_get "$response" "error.details.task_before.owner" || true)
|
|
257
|
+
status_before=$(bridge_json_get "$response" "error.details.task_before.status" || true)
|
|
258
|
+
|
|
259
|
+
case "$ok:$error_code" in
|
|
260
|
+
true:*) ;;
|
|
261
|
+
false:CLAIM_CONFLICT)
|
|
262
|
+
if [[ "$owner_before" == "$TFX_TEAM_AGENT_NAME" && "$status_before" == "in_progress" ]]; then
|
|
263
|
+
echo "[tfx-route] 동일 owner(${TFX_TEAM_AGENT_NAME})가 이미 claim한 task ${TFX_TEAM_TASK_ID} — 계속 실행." >&2
|
|
264
|
+
return 0
|
|
265
|
+
fi
|
|
266
|
+
echo "[tfx-route] CLAIM_CONFLICT: task ${TFX_TEAM_TASK_ID}가 이미 claim됨(owner=${owner_before:-unknown}, status=${status_before:-unknown}). 실행 중단." >&2
|
|
267
|
+
team_send_message \
|
|
268
|
+
"task ${TFX_TEAM_TASK_ID} claim conflict: owner=${owner_before:-unknown}, status=${status_before:-unknown}" \
|
|
269
|
+
"task ${TFX_TEAM_TASK_ID} claim conflict"
|
|
102
270
|
exit 0 ;;
|
|
103
|
-
|
|
104
|
-
|
|
271
|
+
:|false:)
|
|
272
|
+
# Hub 연결 실패 → 자동 재시작 시도 후 claim 재시도
|
|
273
|
+
if try_restart_hub; then
|
|
274
|
+
response=$(bridge_cli team-task-update \
|
|
275
|
+
--team "$TFX_TEAM_NAME" \
|
|
276
|
+
--task-id "$TFX_TEAM_TASK_ID" \
|
|
277
|
+
--claim \
|
|
278
|
+
--owner "$TFX_TEAM_AGENT_NAME" \
|
|
279
|
+
--status in_progress || true)
|
|
280
|
+
ok=$(bridge_json_get "$response" "ok" || true)
|
|
281
|
+
if [[ "$ok" == "true" ]]; then
|
|
282
|
+
echo "[tfx-route] Hub 재시작 후 claim 성공." >&2
|
|
283
|
+
else
|
|
284
|
+
echo "[tfx-route] 경고: Hub 재시작 후 claim 실패. claim 없이 계속 실행." >&2
|
|
285
|
+
fi
|
|
286
|
+
else
|
|
287
|
+
echo "[tfx-route] 경고: Hub 연결 실패 (미실행?). claim 없이 계속 실행." >&2
|
|
288
|
+
fi ;;
|
|
105
289
|
*)
|
|
106
|
-
echo "[tfx-route] 경고: Hub claim
|
|
290
|
+
echo "[tfx-route] 경고: Hub claim 실패 (${error_code:-unknown}${error_message:+: ${error_message}}). claim 없이 계속 실행." >&2 ;;
|
|
107
291
|
esac
|
|
108
292
|
}
|
|
109
293
|
|
|
@@ -112,32 +296,51 @@ team_complete_task() {
|
|
|
112
296
|
local result_summary="${2:-작업 완료}"
|
|
113
297
|
[[ -z "$TFX_TEAM_NAME" || -z "$TFX_TEAM_TASK_ID" ]] && return 0
|
|
114
298
|
|
|
115
|
-
local
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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")
|
|
299
|
+
local summary_trimmed metadata_patch result_payload
|
|
300
|
+
summary_trimmed=$(echo "$result_summary" | head -c 4096)
|
|
301
|
+
metadata_patch=$(bridge_json_stringify metadata-patch "$result" "$summary_trimmed" 2>/dev/null || true)
|
|
302
|
+
result_payload=$(bridge_json_stringify task-result "$TFX_TEAM_TASK_ID" "$result" 2>/dev/null || true)
|
|
122
303
|
|
|
123
304
|
# task 상태: 항상 "completed" (Claude Code API는 "failed" 미지원)
|
|
124
305
|
# 실제 결과는 metadata.result로 전달
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
306
|
+
if [[ -n "$metadata_patch" ]]; then
|
|
307
|
+
if ! bridge_cli_with_restart "팀 task 완료 보고" "Hub 재시작 후 팀 task 완료 보고 성공." \
|
|
308
|
+
team-task-update \
|
|
309
|
+
--team "$TFX_TEAM_NAME" \
|
|
310
|
+
--task-id "$TFX_TEAM_TASK_ID" \
|
|
311
|
+
--status completed \
|
|
312
|
+
--owner "$TFX_TEAM_AGENT_NAME" \
|
|
313
|
+
--metadata-patch "$metadata_patch"; then
|
|
314
|
+
echo "[tfx-route] 경고: 팀 task 완료 보고 실패 (team=$TFX_TEAM_NAME, task=$TFX_TEAM_TASK_ID, result=$result)" >&2
|
|
315
|
+
fi
|
|
316
|
+
fi
|
|
129
317
|
|
|
130
318
|
# 리드에게 메시지 전송
|
|
131
|
-
|
|
132
|
-
-H "Content-Type: application/json" \
|
|
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}\"}" \
|
|
134
|
-
>/dev/null 2>&1 || true
|
|
319
|
+
team_send_message "$summary_trimmed" "task ${TFX_TEAM_TASK_ID} ${result}"
|
|
135
320
|
|
|
136
321
|
# Hub result 발행 (poll_messages 채널 활성화)
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
322
|
+
if [[ -n "$result_payload" ]]; then
|
|
323
|
+
if ! bridge_cli_with_restart "Hub result 발행" "Hub 재시작 후 Hub result 발행 성공." \
|
|
324
|
+
result \
|
|
325
|
+
--agent "$TFX_TEAM_AGENT_NAME" \
|
|
326
|
+
--topic task.result \
|
|
327
|
+
--payload "$result_payload" \
|
|
328
|
+
--trace "$TFX_TEAM_NAME"; then
|
|
329
|
+
echo "[tfx-route] 경고: Hub result 발행 실패 (agent=$TFX_TEAM_AGENT_NAME, task=$TFX_TEAM_TASK_ID)" >&2
|
|
330
|
+
fi
|
|
331
|
+
fi
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
capture_workspace_signature() {
|
|
335
|
+
if ! command -v git &>/dev/null; then
|
|
336
|
+
return 1
|
|
337
|
+
fi
|
|
338
|
+
|
|
339
|
+
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
340
|
+
return 1
|
|
341
|
+
fi
|
|
342
|
+
|
|
343
|
+
git status --short --untracked-files=all --ignore-submodules=all 2>/dev/null || return 1
|
|
141
344
|
}
|
|
142
345
|
|
|
143
346
|
# ── 라우팅 테이블 ──
|
|
@@ -253,6 +456,7 @@ route_agent() {
|
|
|
253
456
|
# ── CLI 모드 오버라이드 (tfx-codex / tfx-gemini 스킬용) ──
|
|
254
457
|
TFX_CLI_MODE="${TFX_CLI_MODE:-auto}"
|
|
255
458
|
TFX_NO_CLAUDE_NATIVE="${TFX_NO_CLAUDE_NATIVE:-0}"
|
|
459
|
+
TFX_VERIFIER_OVERRIDE="${TFX_VERIFIER_OVERRIDE:-auto}"
|
|
256
460
|
TFX_CODEX_TRANSPORT="${TFX_CODEX_TRANSPORT:-auto}"
|
|
257
461
|
TFX_WORKER_INDEX="${TFX_WORKER_INDEX:-}"
|
|
258
462
|
TFX_SEARCH_TOOL="${TFX_SEARCH_TOOL:-}"
|
|
@@ -270,6 +474,13 @@ case "$TFX_CODEX_TRANSPORT" in
|
|
|
270
474
|
exit 1
|
|
271
475
|
;;
|
|
272
476
|
esac
|
|
477
|
+
case "$TFX_VERIFIER_OVERRIDE" in
|
|
478
|
+
auto|claude) ;;
|
|
479
|
+
*)
|
|
480
|
+
echo "ERROR: TFX_VERIFIER_OVERRIDE 값은 auto 또는 claude여야 합니다. (현재: $TFX_VERIFIER_OVERRIDE)" >&2
|
|
481
|
+
exit 1
|
|
482
|
+
;;
|
|
483
|
+
esac
|
|
273
484
|
case "$TFX_WORKER_INDEX" in
|
|
274
485
|
"") ;;
|
|
275
486
|
*[!0-9]*|0)
|
|
@@ -391,8 +602,33 @@ apply_no_claude_native_mode() {
|
|
|
391
602
|
echo "[tfx-route] TFX_NO_CLAUDE_NATIVE=1: $AGENT_TYPE -> codex($CLI_EFFORT) 리매핑" >&2
|
|
392
603
|
}
|
|
393
604
|
|
|
605
|
+
apply_verifier_override() {
|
|
606
|
+
[[ "$AGENT_TYPE" != "verifier" ]] && return
|
|
607
|
+
|
|
608
|
+
case "$TFX_VERIFIER_OVERRIDE" in
|
|
609
|
+
auto|"")
|
|
610
|
+
return 0
|
|
611
|
+
;;
|
|
612
|
+
claude)
|
|
613
|
+
ORIGINAL_AGENT="${ORIGINAL_AGENT:-$AGENT_TYPE}"
|
|
614
|
+
CLI_TYPE="claude-native"; CLI_CMD=""; CLI_ARGS=""
|
|
615
|
+
CLI_EFFORT="n/a"; DEFAULT_TIMEOUT=1200; RUN_MODE="fg"; OPUS_OVERSIGHT="false"
|
|
616
|
+
echo "[tfx-route] TFX_VERIFIER_OVERRIDE=claude: verifier -> claude-native" >&2
|
|
617
|
+
;;
|
|
618
|
+
esac
|
|
619
|
+
|
|
620
|
+
return 0
|
|
621
|
+
}
|
|
622
|
+
|
|
394
623
|
# ── MCP 인벤토리 캐시 ──
|
|
395
624
|
MCP_CACHE="${HOME}/.claude/cache/mcp-inventory.json"
|
|
625
|
+
MCP_FILTER_SCRIPT=""
|
|
626
|
+
MCP_PROFILE_REQUESTED="auto"
|
|
627
|
+
MCP_RESOLVED_PROFILE="default"
|
|
628
|
+
MCP_HINT=""
|
|
629
|
+
GEMINI_ALLOWED_SERVERS=()
|
|
630
|
+
CODEX_CONFIG_FLAGS=()
|
|
631
|
+
CODEX_CONFIG_JSON=""
|
|
396
632
|
|
|
397
633
|
get_cached_servers() {
|
|
398
634
|
local cli_type="$1"
|
|
@@ -401,119 +637,71 @@ get_cached_servers() {
|
|
|
401
637
|
fi
|
|
402
638
|
}
|
|
403
639
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
# auto → 구체 프로필 해석
|
|
410
|
-
if [[ "$profile" == "auto" ]]; then
|
|
411
|
-
case "$agent" in
|
|
412
|
-
executor|build-fixer|debugger|deep-executor) profile="implement" ;;
|
|
413
|
-
architect|planner|critic|analyst) profile="analyze" ;;
|
|
414
|
-
code-reviewer|security-reviewer|quality-reviewer) profile="review" ;;
|
|
415
|
-
scientist|document-specialist) profile="analyze" ;;
|
|
416
|
-
designer|writer) profile="docs" ;;
|
|
417
|
-
*) profile="minimal" ;;
|
|
418
|
-
esac
|
|
640
|
+
resolve_mcp_filter_script() {
|
|
641
|
+
if [[ -n "$MCP_FILTER_SCRIPT" && -f "$MCP_FILTER_SCRIPT" ]]; then
|
|
642
|
+
printf '%s\n' "$MCP_FILTER_SCRIPT"
|
|
643
|
+
return 0
|
|
419
644
|
fi
|
|
420
645
|
|
|
421
|
-
|
|
422
|
-
local
|
|
423
|
-
servers=$(get_cached_servers "$CLI_TYPE")
|
|
424
|
-
[[ -z "$servers" ]] && servers="context7,brave-search,exa,tavily,playwright,sequential-thinking"
|
|
646
|
+
local script_ref script_dir candidate
|
|
647
|
+
local -a candidates=()
|
|
425
648
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
local tool
|
|
649
|
+
script_ref="$(normalize_script_path "${BASH_SOURCE[0]}")"
|
|
650
|
+
if [[ -n "$script_ref" ]]; then
|
|
651
|
+
script_dir="$(cd "$(dirname "$script_ref")" 2>/dev/null && pwd -P || true)"
|
|
652
|
+
[[ -n "$script_dir" ]] && candidates+=("$script_dir/lib/mcp-filter.mjs")
|
|
653
|
+
fi
|
|
432
654
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
655
|
+
candidates+=(
|
|
656
|
+
"$PWD/scripts/lib/mcp-filter.mjs"
|
|
657
|
+
"$PWD/lib/mcp-filter.mjs"
|
|
658
|
+
)
|
|
436
659
|
|
|
437
|
-
|
|
660
|
+
for candidate in "${candidates[@]}"; do
|
|
661
|
+
if [[ -f "$candidate" ]]; then
|
|
662
|
+
MCP_FILTER_SCRIPT="$candidate"
|
|
663
|
+
printf '%s\n' "$MCP_FILTER_SCRIPT"
|
|
438
664
|
return 0
|
|
439
665
|
fi
|
|
666
|
+
done
|
|
440
667
|
|
|
441
|
-
|
|
442
|
-
|
|
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
|
-
}
|
|
668
|
+
return 1
|
|
669
|
+
}
|
|
461
670
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
671
|
+
resolve_mcp_policy() {
|
|
672
|
+
local filter_script available_servers
|
|
673
|
+
if ! filter_script=$(resolve_mcp_filter_script); then
|
|
674
|
+
echo "[tfx-route] 경고: mcp-filter.mjs를 찾지 못해 기본 MCP 정책을 사용합니다." >&2
|
|
675
|
+
MCP_PROFILE_REQUESTED="$MCP_PROFILE"
|
|
676
|
+
MCP_RESOLVED_PROFILE="$MCP_PROFILE"
|
|
677
|
+
MCP_HINT=""
|
|
678
|
+
GEMINI_ALLOWED_SERVERS=()
|
|
679
|
+
CODEX_CONFIG_FLAGS=()
|
|
680
|
+
CODEX_CONFIG_JSON=""
|
|
681
|
+
return 0
|
|
468
682
|
fi
|
|
469
683
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
implement)
|
|
473
|
-
has_server "context7" && hint+="context7으로 라이브러리 문서를 조회하세요. "
|
|
474
|
-
if [[ ${#ordered_tools[@]} -gt 0 ]]; then
|
|
475
|
-
hint+="웹 검색은 ${ordered_tools[0]}를 사용하세요. "
|
|
476
|
-
fi
|
|
477
|
-
hint+="검색 도구 실패 시 402, 429, 432, 433, quota 에러에서 재시도하지 말고 다음 도구로 전환하세요."
|
|
478
|
-
;;
|
|
479
|
-
analyze)
|
|
480
|
-
has_server "context7" && hint+="context7으로 관련 문서를 조회하세요. "
|
|
481
|
-
[[ -n "$ordered_tools_csv" ]] && hint+="웹 검색 우선순위: ${ordered_tools_csv}. 402, 429, 432, 433, quota 에러 시 즉시 다음 도구로 전환. "
|
|
482
|
-
has_server "playwright" && hint+="모든 검색 실패 시 playwright로 직접 방문 (최대 3 URL). "
|
|
483
|
-
hint+="검색 깊이를 제한하고 결과를 빠르게 요약하세요."
|
|
484
|
-
;;
|
|
485
|
-
review)
|
|
486
|
-
has_server "sequential-thinking" && hint="sequential-thinking으로 체계적으로 분석하세요."
|
|
487
|
-
;;
|
|
488
|
-
docs)
|
|
489
|
-
has_server "context7" && hint+="context7으로 공식 문서를 참조하세요. "
|
|
490
|
-
if [[ ${#ordered_tools[@]} -gt 0 ]]; then
|
|
491
|
-
hint+="추가 검색은 ${ordered_tools[0]}를 사용하세요. "
|
|
492
|
-
fi
|
|
493
|
-
hint+="검색 결과의 출처 URL을 함께 제시하세요."
|
|
494
|
-
;;
|
|
495
|
-
minimal|none) ;;
|
|
496
|
-
esac
|
|
497
|
-
echo "$hint"
|
|
498
|
-
}
|
|
684
|
+
available_servers=$(get_cached_servers "$CLI_TYPE")
|
|
685
|
+
[[ -z "$available_servers" ]] && available_servers="context7,brave-search,exa,tavily,playwright,sequential-thinking"
|
|
499
686
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
687
|
+
local -a cmd=(
|
|
688
|
+
"$NODE_BIN" "$filter_script" shell
|
|
689
|
+
"--agent" "$AGENT_TYPE"
|
|
690
|
+
"--profile" "$MCP_PROFILE"
|
|
691
|
+
"--available" "$available_servers"
|
|
692
|
+
"--inventory-file" "$MCP_CACHE"
|
|
693
|
+
"--task-text" "$PROMPT"
|
|
694
|
+
)
|
|
695
|
+
[[ -n "$TFX_SEARCH_TOOL" ]] && cmd+=("--search-tool" "$TFX_SEARCH_TOOL")
|
|
696
|
+
[[ -n "$TFX_WORKER_INDEX" ]] && cmd+=("--worker-index" "$TFX_WORKER_INDEX")
|
|
697
|
+
|
|
698
|
+
local shell_exports
|
|
699
|
+
if ! shell_exports="$("${cmd[@]}")"; then
|
|
700
|
+
echo "[tfx-route] ERROR: MCP 정책 계산 실패" >&2
|
|
701
|
+
return 1
|
|
702
|
+
fi
|
|
511
703
|
|
|
512
|
-
|
|
513
|
-
local servers
|
|
514
|
-
servers=$(get_gemini_mcp_servers "$1")
|
|
515
|
-
[[ -z "$servers" ]] && return 0
|
|
516
|
-
echo "--allowed-mcp-server-names ${servers// /,}"
|
|
704
|
+
eval "$shell_exports"
|
|
517
705
|
}
|
|
518
706
|
|
|
519
707
|
get_claude_model() {
|
|
@@ -544,8 +732,9 @@ resolve_worker_runner_script() {
|
|
|
544
732
|
return 0
|
|
545
733
|
fi
|
|
546
734
|
|
|
547
|
-
local script_dir
|
|
548
|
-
|
|
735
|
+
local script_ref script_dir
|
|
736
|
+
script_ref="$(normalize_script_path "${BASH_SOURCE[0]}")"
|
|
737
|
+
script_dir="$(cd "$(dirname "$script_ref")" && pwd -P)"
|
|
549
738
|
local candidate="$script_dir/tfx-route-worker.mjs"
|
|
550
739
|
[[ -f "$candidate" ]] || return 1
|
|
551
740
|
printf '%s\n' "$candidate"
|
|
@@ -587,19 +776,32 @@ run_stream_worker() {
|
|
|
587
776
|
run_legacy_gemini() {
|
|
588
777
|
local prompt="$1"
|
|
589
778
|
local use_tee_flag="$2"
|
|
590
|
-
local
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
779
|
+
local -a gemini_args=()
|
|
780
|
+
read -r -a gemini_args <<< "$CLI_ARGS"
|
|
781
|
+
|
|
782
|
+
if [[ ${#GEMINI_ALLOWED_SERVERS[@]} -gt 0 ]]; then
|
|
783
|
+
local gemini_mcp_filter prompt_index=-1
|
|
784
|
+
gemini_mcp_filter=$(IFS=,; echo "${GEMINI_ALLOWED_SERVERS[*]}")
|
|
785
|
+
for i in "${!gemini_args[@]}"; do
|
|
786
|
+
if [[ "${gemini_args[$i]}" == "--prompt" ]]; then
|
|
787
|
+
prompt_index="$i"
|
|
788
|
+
break
|
|
789
|
+
fi
|
|
790
|
+
done
|
|
791
|
+
if [[ "$prompt_index" -ge 0 ]]; then
|
|
792
|
+
gemini_args=(
|
|
793
|
+
"${gemini_args[@]:0:$prompt_index}"
|
|
794
|
+
"--allowed-mcp-server-names" "$gemini_mcp_filter"
|
|
795
|
+
"${gemini_args[@]:$prompt_index}"
|
|
796
|
+
)
|
|
797
|
+
echo "[tfx-route] Gemini MCP 필터: $gemini_mcp_filter" >&2
|
|
798
|
+
fi
|
|
597
799
|
fi
|
|
598
800
|
|
|
599
801
|
if [[ "$use_tee_flag" == "true" ]]; then
|
|
600
|
-
timeout "$TIMEOUT_SEC" $CLI_CMD $gemini_args "$prompt" 2>"$STDERR_LOG" | tee "$STDOUT_LOG" &
|
|
802
|
+
timeout "$TIMEOUT_SEC" "$CLI_CMD" "${gemini_args[@]}" "$prompt" 2>"$STDERR_LOG" | tee "$STDOUT_LOG" &
|
|
601
803
|
else
|
|
602
|
-
timeout "$TIMEOUT_SEC" $CLI_CMD $gemini_args "$prompt" >"$STDOUT_LOG" 2>"$STDERR_LOG" &
|
|
804
|
+
timeout "$TIMEOUT_SEC" "$CLI_CMD" "${gemini_args[@]}" "$prompt" >"$STDOUT_LOG" 2>"$STDERR_LOG" &
|
|
603
805
|
fi
|
|
604
806
|
local pid=$!
|
|
605
807
|
|
|
@@ -622,9 +824,9 @@ run_legacy_gemini() {
|
|
|
622
824
|
wait "$pid" 2>/dev/null
|
|
623
825
|
echo "[tfx-route] Gemini crash 감지, 재시도 중..." >&2
|
|
624
826
|
if [[ "$use_tee_flag" == "true" ]]; then
|
|
625
|
-
timeout "$TIMEOUT_SEC" $CLI_CMD $gemini_args "$prompt" 2>"$STDERR_LOG" | tee "$STDOUT_LOG" &
|
|
827
|
+
timeout "$TIMEOUT_SEC" "$CLI_CMD" "${gemini_args[@]}" "$prompt" 2>"$STDERR_LOG" | tee "$STDOUT_LOG" &
|
|
626
828
|
else
|
|
627
|
-
timeout "$TIMEOUT_SEC" $CLI_CMD $gemini_args "$prompt" >"$STDOUT_LOG" 2>"$STDERR_LOG" &
|
|
829
|
+
timeout "$TIMEOUT_SEC" "$CLI_CMD" "${gemini_args[@]}" "$prompt" >"$STDOUT_LOG" 2>"$STDERR_LOG" &
|
|
628
830
|
fi
|
|
629
831
|
pid=$!
|
|
630
832
|
fi
|
|
@@ -639,8 +841,9 @@ resolve_codex_mcp_script() {
|
|
|
639
841
|
return 0
|
|
640
842
|
fi
|
|
641
843
|
|
|
642
|
-
local script_dir
|
|
643
|
-
|
|
844
|
+
local script_ref script_dir
|
|
845
|
+
script_ref="$(normalize_script_path "${BASH_SOURCE[0]}")"
|
|
846
|
+
script_dir="$(cd "$(dirname "$script_ref")" && pwd -P)"
|
|
644
847
|
local candidates=(
|
|
645
848
|
"$script_dir/hub/workers/codex-mcp.mjs"
|
|
646
849
|
"$script_dir/../hub/workers/codex-mcp.mjs"
|
|
@@ -661,11 +864,16 @@ run_codex_exec() {
|
|
|
661
864
|
local prompt="$1"
|
|
662
865
|
local use_tee_flag="$2"
|
|
663
866
|
local exit_code_local=0
|
|
867
|
+
local -a codex_args=()
|
|
868
|
+
read -r -a codex_args <<< "$CLI_ARGS"
|
|
869
|
+
if [[ ${#CODEX_CONFIG_FLAGS[@]} -gt 0 ]]; then
|
|
870
|
+
codex_args+=("${CODEX_CONFIG_FLAGS[@]}")
|
|
871
|
+
fi
|
|
664
872
|
|
|
665
873
|
if [[ "$use_tee_flag" == "true" ]]; then
|
|
666
|
-
timeout "$TIMEOUT_SEC" $CLI_CMD $
|
|
874
|
+
timeout "$TIMEOUT_SEC" "$CLI_CMD" "${codex_args[@]}" "$prompt" 2>"$STDERR_LOG" | tee "$STDOUT_LOG" || exit_code_local=$?
|
|
667
875
|
else
|
|
668
|
-
timeout "$TIMEOUT_SEC" $CLI_CMD $
|
|
876
|
+
timeout "$TIMEOUT_SEC" "$CLI_CMD" "${codex_args[@]}" "$prompt" >"$STDOUT_LOG" 2>"$STDERR_LOG" || exit_code_local=$?
|
|
669
877
|
fi
|
|
670
878
|
|
|
671
879
|
if [[ ! -s "$STDOUT_LOG" && -s "$STDERR_LOG" ]]; then
|
|
@@ -723,6 +931,10 @@ run_codex_mcp() {
|
|
|
723
931
|
"--codex-command" "$CODEX_BIN"
|
|
724
932
|
)
|
|
725
933
|
|
|
934
|
+
if [[ -n "$CODEX_CONFIG_JSON" && "$CODEX_CONFIG_JSON" != "{}" ]]; then
|
|
935
|
+
mcp_args+=("--config-json" "$CODEX_CONFIG_JSON")
|
|
936
|
+
fi
|
|
937
|
+
|
|
726
938
|
case "$AGENT_TYPE" in
|
|
727
939
|
code-reviewer)
|
|
728
940
|
mcp_args+=(
|
|
@@ -767,6 +979,7 @@ main() {
|
|
|
767
979
|
route_agent "$AGENT_TYPE"
|
|
768
980
|
apply_cli_mode
|
|
769
981
|
apply_no_claude_native_mode
|
|
982
|
+
apply_verifier_override
|
|
770
983
|
|
|
771
984
|
# CLI 경로 해석
|
|
772
985
|
case "$CLI_CMD" in
|
|
@@ -811,6 +1024,8 @@ ${ctx_content}
|
|
|
811
1024
|
</prior_context>"
|
|
812
1025
|
fi
|
|
813
1026
|
|
|
1027
|
+
resolve_mcp_policy
|
|
1028
|
+
|
|
814
1029
|
# Claude native는 팀 비-TTY 환경에서 subprocess wrapper를 우선 시도
|
|
815
1030
|
if [[ "$CLI_TYPE" == "claude-native" && -n "$TFX_TEAM_NAME" ]]; then
|
|
816
1031
|
if { [[ ! -t 0 ]] || [[ ! -t 1 ]]; } && command -v "$CLAUDE_BIN" &>/dev/null && resolve_worker_runner_script >/dev/null 2>&1; then
|
|
@@ -822,22 +1037,37 @@ ${ctx_content}
|
|
|
822
1037
|
fi
|
|
823
1038
|
fi
|
|
824
1039
|
|
|
825
|
-
# Claude 네이티브 에이전트는 이 스크립트로 처리 불가
|
|
1040
|
+
# Claude 네이티브 에이전트는 이 스크립트로 처리 불가
|
|
826
1041
|
if [[ "$CLI_TYPE" == "claude-native" ]]; then
|
|
1042
|
+
if [[ -n "$TFX_TEAM_NAME" ]]; then
|
|
1043
|
+
# 팀 모드: Hub에 fallback 필요 시그널 전송 후 구조화된 출력
|
|
1044
|
+
echo "[tfx-route] claude-native 역할($AGENT_TYPE)은 tfx-route.sh로 실행 불가 — Claude Agent fallback 필요" >&2
|
|
1045
|
+
team_complete_task "fallback" "claude-native 역할 실행 불가: ${AGENT_TYPE}. Claude Task(sonnet) 에이전트로 위임하세요."
|
|
1046
|
+
cat <<FALLBACK_EOF
|
|
1047
|
+
=== TFX_NEEDS_FALLBACK ===
|
|
1048
|
+
agent_type: ${AGENT_TYPE}
|
|
1049
|
+
reason: claude-native roles require Claude Agent tools (Read/Edit/Grep). tfx-route.sh cannot provide these.
|
|
1050
|
+
action: Lead should spawn Agent(subagent_type="${AGENT_TYPE}") for this task.
|
|
1051
|
+
task_id: ${TFX_TEAM_TASK_ID:-none}
|
|
1052
|
+
FALLBACK_EOF
|
|
1053
|
+
exit 0
|
|
1054
|
+
fi
|
|
827
1055
|
emit_claude_native_metadata
|
|
828
1056
|
exit 0
|
|
829
1057
|
fi
|
|
830
1058
|
|
|
831
|
-
# MCP 힌트 주입
|
|
832
|
-
local mcp_hint
|
|
833
|
-
mcp_hint=$(get_mcp_hint "$MCP_PROFILE" "$AGENT_TYPE")
|
|
834
1059
|
local FULL_PROMPT="$PROMPT"
|
|
835
|
-
[[ -n "$
|
|
1060
|
+
[[ -n "$MCP_HINT" ]] && FULL_PROMPT="${PROMPT}. ${MCP_HINT}"
|
|
836
1061
|
local codex_transport_effective="n/a"
|
|
837
1062
|
|
|
838
1063
|
# 메타정보 (stderr)
|
|
839
1064
|
echo "[tfx-route] v${VERSION} type=$CLI_TYPE agent=$AGENT_TYPE effort=$CLI_EFFORT mode=$RUN_MODE timeout=${TIMEOUT_SEC}s" >&2
|
|
840
|
-
echo "[tfx-route] opus_oversight=$OPUS_OVERSIGHT mcp_profile=$MCP_PROFILE" >&2
|
|
1065
|
+
echo "[tfx-route] opus_oversight=$OPUS_OVERSIGHT mcp_profile=$MCP_PROFILE resolved_profile=$MCP_RESOLVED_PROFILE verifier_override=$TFX_VERIFIER_OVERRIDE" >&2
|
|
1066
|
+
if [[ ${#GEMINI_ALLOWED_SERVERS[@]} -gt 0 ]]; then
|
|
1067
|
+
echo "[tfx-route] allowed_mcp_servers=$(IFS=,; echo "${GEMINI_ALLOWED_SERVERS[*]}")" >&2
|
|
1068
|
+
else
|
|
1069
|
+
echo "[tfx-route] allowed_mcp_servers=none" >&2
|
|
1070
|
+
fi
|
|
841
1071
|
if [[ -n "$TFX_WORKER_INDEX" || -n "$TFX_SEARCH_TOOL" ]]; then
|
|
842
1072
|
echo "[tfx-route] worker_index=${TFX_WORKER_INDEX:-auto} search_tool=${TFX_SEARCH_TOOL:-auto}" >&2
|
|
843
1073
|
fi
|
|
@@ -851,11 +1081,18 @@ ${ctx_content}
|
|
|
851
1081
|
|
|
852
1082
|
# 팀 모드: task claim
|
|
853
1083
|
team_claim_task
|
|
1084
|
+
team_send_message "작업 시작: ${TFX_TEAM_AGENT_NAME}" "task ${TFX_TEAM_TASK_ID} started"
|
|
854
1085
|
|
|
855
1086
|
# CLI 실행 (stderr 분리 + 타임아웃 + 소요시간 측정)
|
|
856
1087
|
local exit_code=0
|
|
857
1088
|
local start_time
|
|
858
1089
|
start_time=$(date +%s)
|
|
1090
|
+
local workspace_signature_before=""
|
|
1091
|
+
local workspace_signature_after=""
|
|
1092
|
+
local workspace_probe_supported=false
|
|
1093
|
+
if workspace_signature_before=$(capture_workspace_signature); then
|
|
1094
|
+
workspace_probe_supported=true
|
|
1095
|
+
fi
|
|
859
1096
|
|
|
860
1097
|
# tee 활성화 조건: 팀 모드 + 실제 터미널(TTY/tmux)
|
|
861
1098
|
# Agent 래퍼 안에서는 가상 stdout 캡처로 tee 출력이 사용자에게 안 보임 → 파일 전용
|
|
@@ -899,8 +1136,6 @@ ${ctx_content}
|
|
|
899
1136
|
}
|
|
900
1137
|
}
|
|
901
1138
|
}' <<< "$CLI_ARGS")
|
|
902
|
-
local gemini_servers
|
|
903
|
-
gemini_servers=$(get_gemini_mcp_servers "$MCP_PROFILE")
|
|
904
1139
|
local -a gemini_worker_args=(
|
|
905
1140
|
"--command" "$CLI_CMD"
|
|
906
1141
|
"--command-args-json" "$GEMINI_BIN_ARGS_JSON"
|
|
@@ -908,10 +1143,10 @@ ${ctx_content}
|
|
|
908
1143
|
"--approval-mode" "yolo"
|
|
909
1144
|
)
|
|
910
1145
|
|
|
911
|
-
if [[ -
|
|
912
|
-
echo "[tfx-route] Gemini MCP 서버: ${
|
|
1146
|
+
if [[ ${#GEMINI_ALLOWED_SERVERS[@]} -gt 0 ]]; then
|
|
1147
|
+
echo "[tfx-route] Gemini MCP 서버: $(IFS=' '; echo "${GEMINI_ALLOWED_SERVERS[*]}")" >&2
|
|
913
1148
|
local server_name
|
|
914
|
-
for server_name in $
|
|
1149
|
+
for server_name in "${GEMINI_ALLOWED_SERVERS[@]}"; do
|
|
915
1150
|
gemini_worker_args+=("--allowed-mcp-server-name" "$server_name")
|
|
916
1151
|
done
|
|
917
1152
|
fi
|
|
@@ -952,6 +1187,24 @@ EOF
|
|
|
952
1187
|
end_time=$(date +%s)
|
|
953
1188
|
local elapsed=$((end_time - start_time))
|
|
954
1189
|
|
|
1190
|
+
if [[ "$exit_code" -eq 0 ]]; then
|
|
1191
|
+
local workspace_changed="unknown"
|
|
1192
|
+
if [[ "$workspace_probe_supported" == "true" ]]; then
|
|
1193
|
+
if workspace_signature_after=$(capture_workspace_signature); then
|
|
1194
|
+
if [[ "$workspace_signature_before" != "$workspace_signature_after" ]]; then
|
|
1195
|
+
workspace_changed="yes"
|
|
1196
|
+
else
|
|
1197
|
+
workspace_changed="no"
|
|
1198
|
+
fi
|
|
1199
|
+
fi
|
|
1200
|
+
fi
|
|
1201
|
+
|
|
1202
|
+
if [[ ! -s "$STDOUT_LOG" && "$workspace_changed" == "no" ]]; then
|
|
1203
|
+
printf '%s\n' "[tfx-route] exit 0 이지만 stdout 비어있고 워크스페이스 변화가 없습니다. no-op 성공을 실패로 승격합니다." >> "$STDERR_LOG"
|
|
1204
|
+
exit_code=68
|
|
1205
|
+
fi
|
|
1206
|
+
fi
|
|
1207
|
+
|
|
955
1208
|
# 팀 모드: task complete + 리드 보고
|
|
956
1209
|
if [[ -n "$TFX_TEAM_NAME" ]]; then
|
|
957
1210
|
if [[ "$exit_code" -eq 0 ]]; then
|
|
@@ -997,6 +1250,8 @@ EOF
|
|
|
997
1250
|
echo "=== OUTPUT ==="
|
|
998
1251
|
cat "$STDOUT_LOG" 2>/dev/null | head -c "$MAX_STDOUT_BYTES"
|
|
999
1252
|
fi
|
|
1253
|
+
|
|
1254
|
+
return "$exit_code"
|
|
1000
1255
|
}
|
|
1001
1256
|
|
|
1002
1257
|
main
|