triflux 3.3.0-dev.3 → 3.3.0-dev.6
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 +289 -98
- package/hub/pipe.mjs +81 -0
- package/hub/server.mjs +159 -133
- 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/team/nativeProxy.mjs +173 -72
- package/hub/tools.mjs +6 -6
- package/hub/workers/delegator-mcp.mjs +285 -140
- package/package.json +60 -60
- package/scripts/completions/tfx.bash +47 -0
- package/scripts/completions/tfx.fish +44 -0
- package/scripts/completions/tfx.zsh +83 -0
- package/scripts/lib/mcp-filter.mjs +642 -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 +504 -180
- package/skills/tfx-auto/SKILL.md +6 -1
package/scripts/tfx-route.sh
CHANGED
|
@@ -48,16 +48,76 @@ 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=""
|
|
55
56
|
ORIGINAL_CLI_ARGS=""
|
|
56
57
|
|
|
58
|
+
# JSON 문자열 이스케이프:
|
|
59
|
+
# - "\", """ 필수 이스케이프
|
|
60
|
+
# - 제어문자 U+0000..U+001F 이스케이프
|
|
61
|
+
# - 비ASCII 문자는 \uXXXX(또는 surrogate pair)로 강제
|
|
62
|
+
json_escape() {
|
|
63
|
+
local s="${1:-}"
|
|
64
|
+
|
|
65
|
+
if command -v "$NODE_BIN" &>/dev/null; then
|
|
66
|
+
"$NODE_BIN" -e '
|
|
67
|
+
const input = process.argv[1] ?? "";
|
|
68
|
+
let out = "";
|
|
69
|
+
for (const ch of input) {
|
|
70
|
+
const cp = ch.codePointAt(0);
|
|
71
|
+
if (cp === 0x22) { out += "\\\""; continue; } // "
|
|
72
|
+
if (cp === 0x5c) { out += "\\\\"; continue; } // \
|
|
73
|
+
if (cp <= 0x1f) {
|
|
74
|
+
if (cp === 0x08) { out += "\\b"; continue; }
|
|
75
|
+
if (cp === 0x09) { out += "\\t"; continue; }
|
|
76
|
+
if (cp === 0x0a) { out += "\\n"; continue; }
|
|
77
|
+
if (cp === 0x0c) { out += "\\f"; continue; }
|
|
78
|
+
if (cp === 0x0d) { out += "\\r"; continue; }
|
|
79
|
+
out += `\\u${cp.toString(16).padStart(4, "0")}`;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (cp >= 0x20 && cp <= 0x7e) {
|
|
83
|
+
out += ch;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (cp <= 0xffff) {
|
|
87
|
+
out += `\\u${cp.toString(16).padStart(4, "0")}`;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const v = cp - 0x10000;
|
|
91
|
+
const hi = 0xd800 + (v >> 10);
|
|
92
|
+
const lo = 0xdc00 + (v & 0x3ff);
|
|
93
|
+
out += `\\u${hi.toString(16).padStart(4, "0")}\\u${lo.toString(16).padStart(4, "0")}`;
|
|
94
|
+
}
|
|
95
|
+
process.stdout.write(out);
|
|
96
|
+
' -- "$s"
|
|
97
|
+
return
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
echo "[tfx-route] ERROR: node 미설치로 안전한 JSON 이스케이프를 수행할 수 없습니다." >&2
|
|
101
|
+
return 1
|
|
102
|
+
}
|
|
103
|
+
|
|
57
104
|
# ── Per-process 에이전트 등록 (원자적, 락 불필요) ──
|
|
58
105
|
register_agent() {
|
|
59
106
|
local agent_file="${TFX_TMP}/tfx-agent-$$.json"
|
|
60
|
-
|
|
107
|
+
local safe_cli safe_agent started_at
|
|
108
|
+
safe_cli=$(json_escape "$CLI_TYPE" 2>/dev/null || true)
|
|
109
|
+
safe_agent=$(json_escape "$AGENT_TYPE" 2>/dev/null || true)
|
|
110
|
+
started_at=$(date +%s)
|
|
111
|
+
|
|
112
|
+
# fail-closed: 안전 인코딩 불가 시 agent 파일을 쓰지 않는다
|
|
113
|
+
if [[ -n "$CLI_TYPE" && -z "$safe_cli" ]]; then
|
|
114
|
+
return 0
|
|
115
|
+
fi
|
|
116
|
+
if [[ -n "$AGENT_TYPE" && -z "$safe_agent" ]]; then
|
|
117
|
+
return 0
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
printf '{"pid":%s,"cli":"%s","agent":"%s","started":%s}\n' "$$" "$safe_cli" "$safe_agent" "$started_at" \
|
|
61
121
|
> "$agent_file" 2>/dev/null || true
|
|
62
122
|
}
|
|
63
123
|
|
|
@@ -65,45 +125,228 @@ deregister_agent() {
|
|
|
65
125
|
rm -f "${TFX_TMP}/tfx-agent-$$.json" 2>/dev/null || true
|
|
66
126
|
}
|
|
67
127
|
|
|
128
|
+
normalize_script_path() {
|
|
129
|
+
local path="${1:-}"
|
|
130
|
+
if [[ -z "$path" ]]; then
|
|
131
|
+
return 0
|
|
132
|
+
fi
|
|
133
|
+
|
|
134
|
+
if command -v cygpath &>/dev/null; then
|
|
135
|
+
case "$path" in
|
|
136
|
+
[A-Za-z]:\\*|[A-Za-z]:/*)
|
|
137
|
+
cygpath -u "$path"
|
|
138
|
+
return 0
|
|
139
|
+
;;
|
|
140
|
+
esac
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
printf '%s\n' "$path"
|
|
144
|
+
}
|
|
145
|
+
|
|
68
146
|
# ── 팀 Hub Bridge 통신 ──
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
147
|
+
resolve_bridge_script() {
|
|
148
|
+
if [[ -n "${TFX_BRIDGE_SCRIPT:-}" && -f "$TFX_BRIDGE_SCRIPT" ]]; then
|
|
149
|
+
printf '%s\n' "$TFX_BRIDGE_SCRIPT"
|
|
150
|
+
return 0
|
|
151
|
+
fi
|
|
152
|
+
|
|
153
|
+
local script_dir
|
|
154
|
+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
155
|
+
local candidates=(
|
|
156
|
+
"$script_dir/../hub/bridge.mjs"
|
|
157
|
+
"$script_dir/hub/bridge.mjs"
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
local candidate
|
|
161
|
+
for candidate in "${candidates[@]}"; do
|
|
162
|
+
if [[ -f "$candidate" ]]; then
|
|
163
|
+
printf '%s\n' "$candidate"
|
|
164
|
+
return 0
|
|
165
|
+
fi
|
|
166
|
+
done
|
|
167
|
+
|
|
168
|
+
return 1
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
bridge_cli() {
|
|
172
|
+
if ! command -v "$NODE_BIN" &>/dev/null; then
|
|
173
|
+
return 127
|
|
76
174
|
fi
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
175
|
+
|
|
176
|
+
local bridge_script
|
|
177
|
+
if ! bridge_script=$(resolve_bridge_script); then
|
|
178
|
+
return 127
|
|
179
|
+
fi
|
|
180
|
+
|
|
181
|
+
TFX_HUB_PIPE="$TFX_HUB_PIPE" TFX_HUB_URL="$TFX_HUB_URL" TFX_HUB_TOKEN="${TFX_HUB_TOKEN:-}" \
|
|
182
|
+
"$NODE_BIN" "$bridge_script" "$@" 2>/dev/null
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
bridge_json_get() {
|
|
186
|
+
local json="${1:-}"
|
|
187
|
+
local path="${2:-}"
|
|
188
|
+
[[ -z "$json" || -z "$path" ]] && return 1
|
|
189
|
+
|
|
190
|
+
"$NODE_BIN" -e '
|
|
191
|
+
const data = JSON.parse(process.argv[1] || "{}");
|
|
192
|
+
const keys = String(process.argv[2] || "").split(".").filter(Boolean);
|
|
193
|
+
let value = data;
|
|
194
|
+
for (const key of keys) value = value?.[key];
|
|
195
|
+
if (value === undefined || value === null) process.exit(1);
|
|
196
|
+
process.stdout.write(typeof value === "object" ? JSON.stringify(value) : String(value));
|
|
197
|
+
' -- "$json" "$path" 2>/dev/null
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
bridge_json_stringify() {
|
|
201
|
+
local mode="${1:-}"
|
|
202
|
+
shift || true
|
|
203
|
+
|
|
204
|
+
case "$mode" in
|
|
205
|
+
metadata-patch)
|
|
206
|
+
"$NODE_BIN" -e '
|
|
207
|
+
process.stdout.write(JSON.stringify({
|
|
208
|
+
result: process.argv[1] || "",
|
|
209
|
+
summary: process.argv[2] || "",
|
|
210
|
+
}));
|
|
211
|
+
' -- "${1:-}" "${2:-}"
|
|
212
|
+
;;
|
|
213
|
+
task-result)
|
|
214
|
+
"$NODE_BIN" -e '
|
|
215
|
+
process.stdout.write(JSON.stringify({
|
|
216
|
+
task_id: process.argv[1] || "",
|
|
217
|
+
result: process.argv[2] || "",
|
|
218
|
+
}));
|
|
219
|
+
' -- "${1:-}" "${2:-}"
|
|
220
|
+
;;
|
|
221
|
+
*)
|
|
222
|
+
return 1
|
|
223
|
+
;;
|
|
224
|
+
esac
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
team_send_message() {
|
|
228
|
+
local text="${1:-}"
|
|
229
|
+
local summary="${2:-}"
|
|
230
|
+
[[ -z "$TFX_TEAM_NAME" || -z "$text" ]] && return 0
|
|
231
|
+
|
|
232
|
+
if ! bridge_cli_with_restart "팀 메시지 전송" "Hub 재시작 후 팀 메시지 전송 성공." \
|
|
233
|
+
team-send-message \
|
|
234
|
+
--team "$TFX_TEAM_NAME" \
|
|
235
|
+
--from "$TFX_TEAM_AGENT_NAME" \
|
|
236
|
+
--to "$TFX_TEAM_LEAD_NAME" \
|
|
237
|
+
--text "$text" \
|
|
238
|
+
--summary "${summary:-status update}"; then
|
|
239
|
+
echo "[tfx-route] 경고: 팀 메시지 전송 실패 (team=$TFX_TEAM_NAME, to=$TFX_TEAM_LEAD_NAME)" >&2
|
|
240
|
+
return 0
|
|
241
|
+
fi
|
|
242
|
+
|
|
243
|
+
return 0
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
# ── Hub 자동 재시작 (슬립 복귀 등으로 Hub 종료 시) ──
|
|
247
|
+
try_restart_hub() {
|
|
248
|
+
local hub_server script_dir hub_port
|
|
249
|
+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
250
|
+
hub_server="$script_dir/../hub/server.mjs"
|
|
251
|
+
|
|
252
|
+
if [[ ! -f "$hub_server" ]]; then
|
|
253
|
+
echo "[tfx-route] Hub 서버 스크립트 미발견: $hub_server" >&2
|
|
254
|
+
return 1
|
|
255
|
+
fi
|
|
256
|
+
|
|
257
|
+
# TFX_HUB_URL에서 포트 추출 (기본 27888)
|
|
258
|
+
hub_port="${TFX_HUB_URL##*:}"
|
|
259
|
+
hub_port="${hub_port%%/*}"
|
|
260
|
+
[[ -z "$hub_port" || "$hub_port" == "$TFX_HUB_URL" ]] && hub_port=27888
|
|
261
|
+
|
|
262
|
+
echo "[tfx-route] Hub 미응답 — 자동 재시작 시도 (port=$hub_port)..." >&2
|
|
263
|
+
TFX_HUB_PORT="$hub_port" "$NODE_BIN" "$hub_server" &>/dev/null &
|
|
264
|
+
local hub_pid=$!
|
|
265
|
+
|
|
266
|
+
# 최대 4초 대기 (0.5초 간격)
|
|
267
|
+
local i
|
|
268
|
+
for i in 1 2 3 4 5 6 7 8; do
|
|
269
|
+
sleep 0.5
|
|
270
|
+
if curl -sf "${TFX_HUB_URL}/status" >/dev/null 2>&1; then
|
|
271
|
+
echo "[tfx-route] Hub 재시작 성공 (pid=$hub_pid)" >&2
|
|
272
|
+
return 0
|
|
273
|
+
fi
|
|
274
|
+
done
|
|
275
|
+
|
|
276
|
+
echo "[tfx-route] Hub 재시작 실패 — claim 없이 계속 실행" >&2
|
|
277
|
+
return 1
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
bridge_cli_with_restart() {
|
|
281
|
+
local action_label="${1:-bridge 호출}"
|
|
282
|
+
local success_message="${2:-}"
|
|
283
|
+
shift 2 || true
|
|
284
|
+
|
|
285
|
+
if bridge_cli "$@" >/dev/null 2>&1; then
|
|
286
|
+
return 0
|
|
287
|
+
fi
|
|
288
|
+
|
|
289
|
+
if ! try_restart_hub; then
|
|
290
|
+
return 1
|
|
291
|
+
fi
|
|
292
|
+
|
|
293
|
+
if bridge_cli "$@" >/dev/null 2>&1; then
|
|
294
|
+
[[ -n "$success_message" ]] && echo "[tfx-route] ${success_message}" >&2
|
|
295
|
+
return 0
|
|
296
|
+
fi
|
|
297
|
+
|
|
298
|
+
echo "[tfx-route] 경고: Hub 재시작 후 ${action_label} 재시도 실패." >&2
|
|
299
|
+
return 1
|
|
84
300
|
}
|
|
85
301
|
|
|
86
302
|
team_claim_task() {
|
|
87
303
|
[[ -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
|
-
|
|
304
|
+
local response ok error_code error_message owner_before status_before
|
|
305
|
+
response=$(bridge_cli team-task-update \
|
|
306
|
+
--team "$TFX_TEAM_NAME" \
|
|
307
|
+
--task-id "$TFX_TEAM_TASK_ID" \
|
|
308
|
+
--claim \
|
|
309
|
+
--owner "$TFX_TEAM_AGENT_NAME" \
|
|
310
|
+
--status in_progress || true)
|
|
311
|
+
|
|
312
|
+
ok=$(bridge_json_get "$response" "ok" || true)
|
|
313
|
+
error_code=$(bridge_json_get "$response" "error.code" || true)
|
|
314
|
+
error_message=$(bridge_json_get "$response" "error.message" || true)
|
|
315
|
+
owner_before=$(bridge_json_get "$response" "error.details.task_before.owner" || true)
|
|
316
|
+
status_before=$(bridge_json_get "$response" "error.details.task_before.status" || true)
|
|
317
|
+
|
|
318
|
+
case "$ok:$error_code" in
|
|
319
|
+
true:*) ;;
|
|
320
|
+
false:CLAIM_CONFLICT)
|
|
321
|
+
if [[ "$owner_before" == "$TFX_TEAM_AGENT_NAME" && "$status_before" == "in_progress" ]]; then
|
|
322
|
+
echo "[tfx-route] 동일 owner(${TFX_TEAM_AGENT_NAME})가 이미 claim한 task ${TFX_TEAM_TASK_ID} — 계속 실행." >&2
|
|
323
|
+
return 0
|
|
324
|
+
fi
|
|
325
|
+
echo "[tfx-route] CLAIM_CONFLICT: task ${TFX_TEAM_TASK_ID}가 이미 claim됨(owner=${owner_before:-unknown}, status=${status_before:-unknown}). 실행 중단." >&2
|
|
326
|
+
team_send_message \
|
|
327
|
+
"task ${TFX_TEAM_TASK_ID} claim conflict: owner=${owner_before:-unknown}, status=${status_before:-unknown}" \
|
|
328
|
+
"task ${TFX_TEAM_TASK_ID} claim conflict"
|
|
102
329
|
exit 0 ;;
|
|
103
|
-
|
|
104
|
-
|
|
330
|
+
:|false:)
|
|
331
|
+
# Hub 연결 실패 → 자동 재시작 시도 후 claim 재시도
|
|
332
|
+
if try_restart_hub; then
|
|
333
|
+
response=$(bridge_cli team-task-update \
|
|
334
|
+
--team "$TFX_TEAM_NAME" \
|
|
335
|
+
--task-id "$TFX_TEAM_TASK_ID" \
|
|
336
|
+
--claim \
|
|
337
|
+
--owner "$TFX_TEAM_AGENT_NAME" \
|
|
338
|
+
--status in_progress || true)
|
|
339
|
+
ok=$(bridge_json_get "$response" "ok" || true)
|
|
340
|
+
if [[ "$ok" == "true" ]]; then
|
|
341
|
+
echo "[tfx-route] Hub 재시작 후 claim 성공." >&2
|
|
342
|
+
else
|
|
343
|
+
echo "[tfx-route] 경고: Hub 재시작 후 claim 실패. claim 없이 계속 실행." >&2
|
|
344
|
+
fi
|
|
345
|
+
else
|
|
346
|
+
echo "[tfx-route] 경고: Hub 연결 실패 (미실행?). claim 없이 계속 실행." >&2
|
|
347
|
+
fi ;;
|
|
105
348
|
*)
|
|
106
|
-
echo "[tfx-route] 경고: Hub claim
|
|
349
|
+
echo "[tfx-route] 경고: Hub claim 실패 (${error_code:-unknown}${error_message:+: ${error_message}}). claim 없이 계속 실행." >&2 ;;
|
|
107
350
|
esac
|
|
108
351
|
}
|
|
109
352
|
|
|
@@ -112,32 +355,61 @@ team_complete_task() {
|
|
|
112
355
|
local result_summary="${2:-작업 완료}"
|
|
113
356
|
[[ -z "$TFX_TEAM_NAME" || -z "$TFX_TEAM_TASK_ID" ]] && return 0
|
|
114
357
|
|
|
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")
|
|
358
|
+
local summary_trimmed metadata_patch result_payload
|
|
359
|
+
summary_trimmed=$(echo "$result_summary" | head -c 4096)
|
|
360
|
+
metadata_patch=$(bridge_json_stringify metadata-patch "$result" "$summary_trimmed" 2>/dev/null || true)
|
|
361
|
+
result_payload=$(bridge_json_stringify task-result "$TFX_TEAM_TASK_ID" "$result" 2>/dev/null || true)
|
|
122
362
|
|
|
123
363
|
# task 상태: 항상 "completed" (Claude Code API는 "failed" 미지원)
|
|
124
364
|
# 실제 결과는 metadata.result로 전달
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
365
|
+
if [[ -n "$metadata_patch" ]]; then
|
|
366
|
+
if ! bridge_cli_with_restart "팀 task 완료 보고" "Hub 재시작 후 팀 task 완료 보고 성공." \
|
|
367
|
+
team-task-update \
|
|
368
|
+
--team "$TFX_TEAM_NAME" \
|
|
369
|
+
--task-id "$TFX_TEAM_TASK_ID" \
|
|
370
|
+
--status completed \
|
|
371
|
+
--owner "$TFX_TEAM_AGENT_NAME" \
|
|
372
|
+
--metadata-patch "$metadata_patch"; then
|
|
373
|
+
echo "[tfx-route] 경고: 팀 task 완료 보고 실패 (team=$TFX_TEAM_NAME, task=$TFX_TEAM_TASK_ID, result=$result)" >&2
|
|
374
|
+
fi
|
|
375
|
+
fi
|
|
129
376
|
|
|
130
377
|
# 리드에게 메시지 전송
|
|
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
|
|
378
|
+
team_send_message "$summary_trimmed" "task ${TFX_TEAM_TASK_ID} ${result}"
|
|
135
379
|
|
|
136
380
|
# Hub result 발행 (poll_messages 채널 활성화)
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
381
|
+
if [[ -n "$result_payload" ]]; then
|
|
382
|
+
if ! bridge_cli_with_restart "Hub result 발행" "Hub 재시작 후 Hub result 발행 성공." \
|
|
383
|
+
result \
|
|
384
|
+
--agent "$TFX_TEAM_AGENT_NAME" \
|
|
385
|
+
--topic task.result \
|
|
386
|
+
--payload "$result_payload" \
|
|
387
|
+
--trace "$TFX_TEAM_NAME"; then
|
|
388
|
+
echo "[tfx-route] 경고: Hub result 발행 실패 (agent=$TFX_TEAM_AGENT_NAME, task=$TFX_TEAM_TASK_ID)" >&2
|
|
389
|
+
fi
|
|
390
|
+
fi
|
|
391
|
+
|
|
392
|
+
# 로컬 결과 파일 백업 (세션 끊김 복구용)
|
|
393
|
+
# Claude 재로그인 시 Agent 래퍼가 죽어도 이 파일로 결과 수집 가능
|
|
394
|
+
local result_dir="${TFX_RESULT_DIR:-${HOME}/.claude/tfx-results/${TFX_TEAM_NAME}}"
|
|
395
|
+
if mkdir -p "$result_dir" 2>/dev/null; then
|
|
396
|
+
cat > "${result_dir}/${TFX_TEAM_TASK_ID}.json" 2>/dev/null <<RESULT_EOF
|
|
397
|
+
{"taskId":"${TFX_TEAM_TASK_ID}","agent":"${TFX_TEAM_AGENT_NAME}","team":"${TFX_TEAM_NAME}","result":"${result}","summary":$(printf '%s' "$summary_trimmed" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>console.log(JSON.stringify(d)))" 2>/dev/null || echo '""'),"timestamp":"$(date -u +%Y-%m-%dT%H:%M:%SZ)"}
|
|
398
|
+
RESULT_EOF
|
|
399
|
+
[[ $? -eq 0 ]] && echo "[tfx-route] 결과 백업: ${result_dir}/${TFX_TEAM_TASK_ID}.json" >&2
|
|
400
|
+
fi
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
capture_workspace_signature() {
|
|
404
|
+
if ! command -v git &>/dev/null; then
|
|
405
|
+
return 1
|
|
406
|
+
fi
|
|
407
|
+
|
|
408
|
+
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
409
|
+
return 1
|
|
410
|
+
fi
|
|
411
|
+
|
|
412
|
+
git status --short --untracked-files=all --ignore-submodules=all 2>/dev/null || return 1
|
|
141
413
|
}
|
|
142
414
|
|
|
143
415
|
# ── 라우팅 테이블 ──
|
|
@@ -253,6 +525,7 @@ route_agent() {
|
|
|
253
525
|
# ── CLI 모드 오버라이드 (tfx-codex / tfx-gemini 스킬용) ──
|
|
254
526
|
TFX_CLI_MODE="${TFX_CLI_MODE:-auto}"
|
|
255
527
|
TFX_NO_CLAUDE_NATIVE="${TFX_NO_CLAUDE_NATIVE:-0}"
|
|
528
|
+
TFX_VERIFIER_OVERRIDE="${TFX_VERIFIER_OVERRIDE:-auto}"
|
|
256
529
|
TFX_CODEX_TRANSPORT="${TFX_CODEX_TRANSPORT:-auto}"
|
|
257
530
|
TFX_WORKER_INDEX="${TFX_WORKER_INDEX:-}"
|
|
258
531
|
TFX_SEARCH_TOOL="${TFX_SEARCH_TOOL:-}"
|
|
@@ -270,6 +543,13 @@ case "$TFX_CODEX_TRANSPORT" in
|
|
|
270
543
|
exit 1
|
|
271
544
|
;;
|
|
272
545
|
esac
|
|
546
|
+
case "$TFX_VERIFIER_OVERRIDE" in
|
|
547
|
+
auto|claude) ;;
|
|
548
|
+
*)
|
|
549
|
+
echo "ERROR: TFX_VERIFIER_OVERRIDE 값은 auto 또는 claude여야 합니다. (현재: $TFX_VERIFIER_OVERRIDE)" >&2
|
|
550
|
+
exit 1
|
|
551
|
+
;;
|
|
552
|
+
esac
|
|
273
553
|
case "$TFX_WORKER_INDEX" in
|
|
274
554
|
"") ;;
|
|
275
555
|
*[!0-9]*|0)
|
|
@@ -391,8 +671,33 @@ apply_no_claude_native_mode() {
|
|
|
391
671
|
echo "[tfx-route] TFX_NO_CLAUDE_NATIVE=1: $AGENT_TYPE -> codex($CLI_EFFORT) 리매핑" >&2
|
|
392
672
|
}
|
|
393
673
|
|
|
674
|
+
apply_verifier_override() {
|
|
675
|
+
[[ "$AGENT_TYPE" != "verifier" ]] && return
|
|
676
|
+
|
|
677
|
+
case "$TFX_VERIFIER_OVERRIDE" in
|
|
678
|
+
auto|"")
|
|
679
|
+
return 0
|
|
680
|
+
;;
|
|
681
|
+
claude)
|
|
682
|
+
ORIGINAL_AGENT="${ORIGINAL_AGENT:-$AGENT_TYPE}"
|
|
683
|
+
CLI_TYPE="claude-native"; CLI_CMD=""; CLI_ARGS=""
|
|
684
|
+
CLI_EFFORT="n/a"; DEFAULT_TIMEOUT=1200; RUN_MODE="fg"; OPUS_OVERSIGHT="false"
|
|
685
|
+
echo "[tfx-route] TFX_VERIFIER_OVERRIDE=claude: verifier -> claude-native" >&2
|
|
686
|
+
;;
|
|
687
|
+
esac
|
|
688
|
+
|
|
689
|
+
return 0
|
|
690
|
+
}
|
|
691
|
+
|
|
394
692
|
# ── MCP 인벤토리 캐시 ──
|
|
395
693
|
MCP_CACHE="${HOME}/.claude/cache/mcp-inventory.json"
|
|
694
|
+
MCP_FILTER_SCRIPT=""
|
|
695
|
+
MCP_PROFILE_REQUESTED="auto"
|
|
696
|
+
MCP_RESOLVED_PROFILE="default"
|
|
697
|
+
MCP_HINT=""
|
|
698
|
+
GEMINI_ALLOWED_SERVERS=()
|
|
699
|
+
CODEX_CONFIG_FLAGS=()
|
|
700
|
+
CODEX_CONFIG_JSON=""
|
|
396
701
|
|
|
397
702
|
get_cached_servers() {
|
|
398
703
|
local cli_type="$1"
|
|
@@ -401,119 +706,71 @@ get_cached_servers() {
|
|
|
401
706
|
fi
|
|
402
707
|
}
|
|
403
708
|
|
|
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
|
|
709
|
+
resolve_mcp_filter_script() {
|
|
710
|
+
if [[ -n "$MCP_FILTER_SCRIPT" && -f "$MCP_FILTER_SCRIPT" ]]; then
|
|
711
|
+
printf '%s\n' "$MCP_FILTER_SCRIPT"
|
|
712
|
+
return 0
|
|
419
713
|
fi
|
|
420
714
|
|
|
421
|
-
|
|
422
|
-
local
|
|
423
|
-
servers=$(get_cached_servers "$CLI_TYPE")
|
|
424
|
-
[[ -z "$servers" ]] && servers="context7,brave-search,exa,tavily,playwright,sequential-thinking"
|
|
715
|
+
local script_ref script_dir candidate
|
|
716
|
+
local -a candidates=()
|
|
425
717
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
local tool
|
|
718
|
+
script_ref="$(normalize_script_path "${BASH_SOURCE[0]}")"
|
|
719
|
+
if [[ -n "$script_ref" ]]; then
|
|
720
|
+
script_dir="$(cd "$(dirname "$script_ref")" 2>/dev/null && pwd -P || true)"
|
|
721
|
+
[[ -n "$script_dir" ]] && candidates+=("$script_dir/lib/mcp-filter.mjs")
|
|
722
|
+
fi
|
|
432
723
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
724
|
+
candidates+=(
|
|
725
|
+
"$PWD/scripts/lib/mcp-filter.mjs"
|
|
726
|
+
"$PWD/lib/mcp-filter.mjs"
|
|
727
|
+
)
|
|
436
728
|
|
|
437
|
-
|
|
729
|
+
for candidate in "${candidates[@]}"; do
|
|
730
|
+
if [[ -f "$candidate" ]]; then
|
|
731
|
+
MCP_FILTER_SCRIPT="$candidate"
|
|
732
|
+
printf '%s\n' "$MCP_FILTER_SCRIPT"
|
|
438
733
|
return 0
|
|
439
734
|
fi
|
|
735
|
+
done
|
|
440
736
|
|
|
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
|
-
}
|
|
737
|
+
return 1
|
|
738
|
+
}
|
|
461
739
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
740
|
+
resolve_mcp_policy() {
|
|
741
|
+
local filter_script available_servers
|
|
742
|
+
if ! filter_script=$(resolve_mcp_filter_script); then
|
|
743
|
+
echo "[tfx-route] 경고: mcp-filter.mjs를 찾지 못해 기본 MCP 정책을 사용합니다." >&2
|
|
744
|
+
MCP_PROFILE_REQUESTED="$MCP_PROFILE"
|
|
745
|
+
MCP_RESOLVED_PROFILE="$MCP_PROFILE"
|
|
746
|
+
MCP_HINT=""
|
|
747
|
+
GEMINI_ALLOWED_SERVERS=()
|
|
748
|
+
CODEX_CONFIG_FLAGS=()
|
|
749
|
+
CODEX_CONFIG_JSON=""
|
|
750
|
+
return 0
|
|
468
751
|
fi
|
|
469
752
|
|
|
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
|
-
}
|
|
753
|
+
available_servers=$(get_cached_servers "$CLI_TYPE")
|
|
754
|
+
[[ -z "$available_servers" ]] && available_servers="context7,brave-search,exa,tavily,playwright,sequential-thinking"
|
|
499
755
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
}
|
|
756
|
+
local -a cmd=(
|
|
757
|
+
"$NODE_BIN" "$filter_script" shell
|
|
758
|
+
"--agent" "$AGENT_TYPE"
|
|
759
|
+
"--profile" "$MCP_PROFILE"
|
|
760
|
+
"--available" "$available_servers"
|
|
761
|
+
"--inventory-file" "$MCP_CACHE"
|
|
762
|
+
"--task-text" "$PROMPT"
|
|
763
|
+
)
|
|
764
|
+
[[ -n "$TFX_SEARCH_TOOL" ]] && cmd+=("--search-tool" "$TFX_SEARCH_TOOL")
|
|
765
|
+
[[ -n "$TFX_WORKER_INDEX" ]] && cmd+=("--worker-index" "$TFX_WORKER_INDEX")
|
|
511
766
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
767
|
+
local shell_exports
|
|
768
|
+
if ! shell_exports="$("${cmd[@]}")"; then
|
|
769
|
+
echo "[tfx-route] ERROR: MCP 정책 계산 실패" >&2
|
|
770
|
+
return 1
|
|
771
|
+
fi
|
|
772
|
+
|
|
773
|
+
eval "$shell_exports"
|
|
517
774
|
}
|
|
518
775
|
|
|
519
776
|
get_claude_model() {
|
|
@@ -544,8 +801,9 @@ resolve_worker_runner_script() {
|
|
|
544
801
|
return 0
|
|
545
802
|
fi
|
|
546
803
|
|
|
547
|
-
local script_dir
|
|
548
|
-
|
|
804
|
+
local script_ref script_dir
|
|
805
|
+
script_ref="$(normalize_script_path "${BASH_SOURCE[0]}")"
|
|
806
|
+
script_dir="$(cd "$(dirname "$script_ref")" && pwd -P)"
|
|
549
807
|
local candidate="$script_dir/tfx-route-worker.mjs"
|
|
550
808
|
[[ -f "$candidate" ]] || return 1
|
|
551
809
|
printf '%s\n' "$candidate"
|
|
@@ -587,19 +845,32 @@ run_stream_worker() {
|
|
|
587
845
|
run_legacy_gemini() {
|
|
588
846
|
local prompt="$1"
|
|
589
847
|
local use_tee_flag="$2"
|
|
590
|
-
local
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
848
|
+
local -a gemini_args=()
|
|
849
|
+
read -r -a gemini_args <<< "$CLI_ARGS"
|
|
850
|
+
|
|
851
|
+
if [[ ${#GEMINI_ALLOWED_SERVERS[@]} -gt 0 ]]; then
|
|
852
|
+
local gemini_mcp_filter prompt_index=-1
|
|
853
|
+
gemini_mcp_filter=$(IFS=,; echo "${GEMINI_ALLOWED_SERVERS[*]}")
|
|
854
|
+
for i in "${!gemini_args[@]}"; do
|
|
855
|
+
if [[ "${gemini_args[$i]}" == "--prompt" ]]; then
|
|
856
|
+
prompt_index="$i"
|
|
857
|
+
break
|
|
858
|
+
fi
|
|
859
|
+
done
|
|
860
|
+
if [[ "$prompt_index" -ge 0 ]]; then
|
|
861
|
+
gemini_args=(
|
|
862
|
+
"${gemini_args[@]:0:$prompt_index}"
|
|
863
|
+
"--allowed-mcp-server-names" "$gemini_mcp_filter"
|
|
864
|
+
"${gemini_args[@]:$prompt_index}"
|
|
865
|
+
)
|
|
866
|
+
echo "[tfx-route] Gemini MCP 필터: $gemini_mcp_filter" >&2
|
|
867
|
+
fi
|
|
597
868
|
fi
|
|
598
869
|
|
|
599
870
|
if [[ "$use_tee_flag" == "true" ]]; then
|
|
600
|
-
timeout "$TIMEOUT_SEC" $CLI_CMD $gemini_args "$prompt" 2>"$STDERR_LOG" | tee "$STDOUT_LOG" &
|
|
871
|
+
timeout "$TIMEOUT_SEC" "$CLI_CMD" "${gemini_args[@]}" "$prompt" 2>"$STDERR_LOG" | tee "$STDOUT_LOG" &
|
|
601
872
|
else
|
|
602
|
-
timeout "$TIMEOUT_SEC" $CLI_CMD $gemini_args "$prompt" >"$STDOUT_LOG" 2>"$STDERR_LOG" &
|
|
873
|
+
timeout "$TIMEOUT_SEC" "$CLI_CMD" "${gemini_args[@]}" "$prompt" >"$STDOUT_LOG" 2>"$STDERR_LOG" &
|
|
603
874
|
fi
|
|
604
875
|
local pid=$!
|
|
605
876
|
|
|
@@ -622,9 +893,9 @@ run_legacy_gemini() {
|
|
|
622
893
|
wait "$pid" 2>/dev/null
|
|
623
894
|
echo "[tfx-route] Gemini crash 감지, 재시도 중..." >&2
|
|
624
895
|
if [[ "$use_tee_flag" == "true" ]]; then
|
|
625
|
-
timeout "$TIMEOUT_SEC" $CLI_CMD $gemini_args "$prompt" 2>"$STDERR_LOG" | tee "$STDOUT_LOG" &
|
|
896
|
+
timeout "$TIMEOUT_SEC" "$CLI_CMD" "${gemini_args[@]}" "$prompt" 2>"$STDERR_LOG" | tee "$STDOUT_LOG" &
|
|
626
897
|
else
|
|
627
|
-
timeout "$TIMEOUT_SEC" $CLI_CMD $gemini_args "$prompt" >"$STDOUT_LOG" 2>"$STDERR_LOG" &
|
|
898
|
+
timeout "$TIMEOUT_SEC" "$CLI_CMD" "${gemini_args[@]}" "$prompt" >"$STDOUT_LOG" 2>"$STDERR_LOG" &
|
|
628
899
|
fi
|
|
629
900
|
pid=$!
|
|
630
901
|
fi
|
|
@@ -639,8 +910,9 @@ resolve_codex_mcp_script() {
|
|
|
639
910
|
return 0
|
|
640
911
|
fi
|
|
641
912
|
|
|
642
|
-
local script_dir
|
|
643
|
-
|
|
913
|
+
local script_ref script_dir
|
|
914
|
+
script_ref="$(normalize_script_path "${BASH_SOURCE[0]}")"
|
|
915
|
+
script_dir="$(cd "$(dirname "$script_ref")" && pwd -P)"
|
|
644
916
|
local candidates=(
|
|
645
917
|
"$script_dir/hub/workers/codex-mcp.mjs"
|
|
646
918
|
"$script_dir/../hub/workers/codex-mcp.mjs"
|
|
@@ -661,11 +933,16 @@ run_codex_exec() {
|
|
|
661
933
|
local prompt="$1"
|
|
662
934
|
local use_tee_flag="$2"
|
|
663
935
|
local exit_code_local=0
|
|
936
|
+
local -a codex_args=()
|
|
937
|
+
read -r -a codex_args <<< "$CLI_ARGS"
|
|
938
|
+
if [[ ${#CODEX_CONFIG_FLAGS[@]} -gt 0 ]]; then
|
|
939
|
+
codex_args+=("${CODEX_CONFIG_FLAGS[@]}")
|
|
940
|
+
fi
|
|
664
941
|
|
|
665
942
|
if [[ "$use_tee_flag" == "true" ]]; then
|
|
666
|
-
timeout "$TIMEOUT_SEC" $CLI_CMD $
|
|
943
|
+
timeout "$TIMEOUT_SEC" "$CLI_CMD" "${codex_args[@]}" "$prompt" 2>"$STDERR_LOG" | tee "$STDOUT_LOG" || exit_code_local=$?
|
|
667
944
|
else
|
|
668
|
-
timeout "$TIMEOUT_SEC" $CLI_CMD $
|
|
945
|
+
timeout "$TIMEOUT_SEC" "$CLI_CMD" "${codex_args[@]}" "$prompt" >"$STDOUT_LOG" 2>"$STDERR_LOG" || exit_code_local=$?
|
|
669
946
|
fi
|
|
670
947
|
|
|
671
948
|
if [[ ! -s "$STDOUT_LOG" && -s "$STDERR_LOG" ]]; then
|
|
@@ -723,6 +1000,10 @@ run_codex_mcp() {
|
|
|
723
1000
|
"--codex-command" "$CODEX_BIN"
|
|
724
1001
|
)
|
|
725
1002
|
|
|
1003
|
+
if [[ -n "$CODEX_CONFIG_JSON" && "$CODEX_CONFIG_JSON" != "{}" ]]; then
|
|
1004
|
+
mcp_args+=("--config-json" "$CODEX_CONFIG_JSON")
|
|
1005
|
+
fi
|
|
1006
|
+
|
|
726
1007
|
case "$AGENT_TYPE" in
|
|
727
1008
|
code-reviewer)
|
|
728
1009
|
mcp_args+=(
|
|
@@ -767,6 +1048,7 @@ main() {
|
|
|
767
1048
|
route_agent "$AGENT_TYPE"
|
|
768
1049
|
apply_cli_mode
|
|
769
1050
|
apply_no_claude_native_mode
|
|
1051
|
+
apply_verifier_override
|
|
770
1052
|
|
|
771
1053
|
# CLI 경로 해석
|
|
772
1054
|
case "$CLI_CMD" in
|
|
@@ -811,6 +1093,8 @@ ${ctx_content}
|
|
|
811
1093
|
</prior_context>"
|
|
812
1094
|
fi
|
|
813
1095
|
|
|
1096
|
+
resolve_mcp_policy
|
|
1097
|
+
|
|
814
1098
|
# Claude native는 팀 비-TTY 환경에서 subprocess wrapper를 우선 시도
|
|
815
1099
|
if [[ "$CLI_TYPE" == "claude-native" && -n "$TFX_TEAM_NAME" ]]; then
|
|
816
1100
|
if { [[ ! -t 0 ]] || [[ ! -t 1 ]]; } && command -v "$CLAUDE_BIN" &>/dev/null && resolve_worker_runner_script >/dev/null 2>&1; then
|
|
@@ -822,22 +1106,37 @@ ${ctx_content}
|
|
|
822
1106
|
fi
|
|
823
1107
|
fi
|
|
824
1108
|
|
|
825
|
-
# Claude 네이티브 에이전트는 이 스크립트로 처리 불가
|
|
1109
|
+
# Claude 네이티브 에이전트는 이 스크립트로 처리 불가
|
|
826
1110
|
if [[ "$CLI_TYPE" == "claude-native" ]]; then
|
|
1111
|
+
if [[ -n "$TFX_TEAM_NAME" ]]; then
|
|
1112
|
+
# 팀 모드: Hub에 fallback 필요 시그널 전송 후 구조화된 출력
|
|
1113
|
+
echo "[tfx-route] claude-native 역할($AGENT_TYPE)은 tfx-route.sh로 실행 불가 — Claude Agent fallback 필요" >&2
|
|
1114
|
+
team_complete_task "fallback" "claude-native 역할 실행 불가: ${AGENT_TYPE}. Claude Task(sonnet) 에이전트로 위임하세요."
|
|
1115
|
+
cat <<FALLBACK_EOF
|
|
1116
|
+
=== TFX_NEEDS_FALLBACK ===
|
|
1117
|
+
agent_type: ${AGENT_TYPE}
|
|
1118
|
+
reason: claude-native roles require Claude Agent tools (Read/Edit/Grep). tfx-route.sh cannot provide these.
|
|
1119
|
+
action: Lead should spawn Agent(subagent_type="${AGENT_TYPE}") for this task.
|
|
1120
|
+
task_id: ${TFX_TEAM_TASK_ID:-none}
|
|
1121
|
+
FALLBACK_EOF
|
|
1122
|
+
exit 0
|
|
1123
|
+
fi
|
|
827
1124
|
emit_claude_native_metadata
|
|
828
1125
|
exit 0
|
|
829
1126
|
fi
|
|
830
1127
|
|
|
831
|
-
# MCP 힌트 주입
|
|
832
|
-
local mcp_hint
|
|
833
|
-
mcp_hint=$(get_mcp_hint "$MCP_PROFILE" "$AGENT_TYPE")
|
|
834
1128
|
local FULL_PROMPT="$PROMPT"
|
|
835
|
-
[[ -n "$
|
|
1129
|
+
[[ -n "$MCP_HINT" ]] && FULL_PROMPT="${PROMPT}. ${MCP_HINT}"
|
|
836
1130
|
local codex_transport_effective="n/a"
|
|
837
1131
|
|
|
838
1132
|
# 메타정보 (stderr)
|
|
839
1133
|
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
|
|
1134
|
+
echo "[tfx-route] opus_oversight=$OPUS_OVERSIGHT mcp_profile=$MCP_PROFILE resolved_profile=$MCP_RESOLVED_PROFILE verifier_override=$TFX_VERIFIER_OVERRIDE" >&2
|
|
1135
|
+
if [[ ${#GEMINI_ALLOWED_SERVERS[@]} -gt 0 ]]; then
|
|
1136
|
+
echo "[tfx-route] allowed_mcp_servers=$(IFS=,; echo "${GEMINI_ALLOWED_SERVERS[*]}")" >&2
|
|
1137
|
+
else
|
|
1138
|
+
echo "[tfx-route] allowed_mcp_servers=none" >&2
|
|
1139
|
+
fi
|
|
841
1140
|
if [[ -n "$TFX_WORKER_INDEX" || -n "$TFX_SEARCH_TOOL" ]]; then
|
|
842
1141
|
echo "[tfx-route] worker_index=${TFX_WORKER_INDEX:-auto} search_tool=${TFX_SEARCH_TOOL:-auto}" >&2
|
|
843
1142
|
fi
|
|
@@ -851,11 +1150,18 @@ ${ctx_content}
|
|
|
851
1150
|
|
|
852
1151
|
# 팀 모드: task claim
|
|
853
1152
|
team_claim_task
|
|
1153
|
+
team_send_message "작업 시작: ${TFX_TEAM_AGENT_NAME}" "task ${TFX_TEAM_TASK_ID} started"
|
|
854
1154
|
|
|
855
1155
|
# CLI 실행 (stderr 분리 + 타임아웃 + 소요시간 측정)
|
|
856
1156
|
local exit_code=0
|
|
857
1157
|
local start_time
|
|
858
1158
|
start_time=$(date +%s)
|
|
1159
|
+
local workspace_signature_before=""
|
|
1160
|
+
local workspace_signature_after=""
|
|
1161
|
+
local workspace_probe_supported=false
|
|
1162
|
+
if workspace_signature_before=$(capture_workspace_signature); then
|
|
1163
|
+
workspace_probe_supported=true
|
|
1164
|
+
fi
|
|
859
1165
|
|
|
860
1166
|
# tee 활성화 조건: 팀 모드 + 실제 터미널(TTY/tmux)
|
|
861
1167
|
# Agent 래퍼 안에서는 가상 stdout 캡처로 tee 출력이 사용자에게 안 보임 → 파일 전용
|
|
@@ -899,8 +1205,6 @@ ${ctx_content}
|
|
|
899
1205
|
}
|
|
900
1206
|
}
|
|
901
1207
|
}' <<< "$CLI_ARGS")
|
|
902
|
-
local gemini_servers
|
|
903
|
-
gemini_servers=$(get_gemini_mcp_servers "$MCP_PROFILE")
|
|
904
1208
|
local -a gemini_worker_args=(
|
|
905
1209
|
"--command" "$CLI_CMD"
|
|
906
1210
|
"--command-args-json" "$GEMINI_BIN_ARGS_JSON"
|
|
@@ -908,10 +1212,10 @@ ${ctx_content}
|
|
|
908
1212
|
"--approval-mode" "yolo"
|
|
909
1213
|
)
|
|
910
1214
|
|
|
911
|
-
if [[ -
|
|
912
|
-
echo "[tfx-route] Gemini MCP 서버: ${
|
|
1215
|
+
if [[ ${#GEMINI_ALLOWED_SERVERS[@]} -gt 0 ]]; then
|
|
1216
|
+
echo "[tfx-route] Gemini MCP 서버: $(IFS=' '; echo "${GEMINI_ALLOWED_SERVERS[*]}")" >&2
|
|
913
1217
|
local server_name
|
|
914
|
-
for server_name in $
|
|
1218
|
+
for server_name in "${GEMINI_ALLOWED_SERVERS[@]}"; do
|
|
915
1219
|
gemini_worker_args+=("--allowed-mcp-server-name" "$server_name")
|
|
916
1220
|
done
|
|
917
1221
|
fi
|
|
@@ -952,6 +1256,24 @@ EOF
|
|
|
952
1256
|
end_time=$(date +%s)
|
|
953
1257
|
local elapsed=$((end_time - start_time))
|
|
954
1258
|
|
|
1259
|
+
if [[ "$exit_code" -eq 0 ]]; then
|
|
1260
|
+
local workspace_changed="unknown"
|
|
1261
|
+
if [[ "$workspace_probe_supported" == "true" ]]; then
|
|
1262
|
+
if workspace_signature_after=$(capture_workspace_signature); then
|
|
1263
|
+
if [[ "$workspace_signature_before" != "$workspace_signature_after" ]]; then
|
|
1264
|
+
workspace_changed="yes"
|
|
1265
|
+
else
|
|
1266
|
+
workspace_changed="no"
|
|
1267
|
+
fi
|
|
1268
|
+
fi
|
|
1269
|
+
fi
|
|
1270
|
+
|
|
1271
|
+
if [[ ! -s "$STDOUT_LOG" && "$workspace_changed" == "no" ]]; then
|
|
1272
|
+
printf '%s\n' "[tfx-route] exit 0 이지만 stdout 비어있고 워크스페이스 변화가 없습니다. no-op 성공을 실패로 승격합니다." >> "$STDERR_LOG"
|
|
1273
|
+
exit_code=68
|
|
1274
|
+
fi
|
|
1275
|
+
fi
|
|
1276
|
+
|
|
955
1277
|
# 팀 모드: task complete + 리드 보고
|
|
956
1278
|
if [[ -n "$TFX_TEAM_NAME" ]]; then
|
|
957
1279
|
if [[ "$exit_code" -eq 0 ]]; then
|
|
@@ -997,6 +1319,8 @@ EOF
|
|
|
997
1319
|
echo "=== OUTPUT ==="
|
|
998
1320
|
cat "$STDOUT_LOG" 2>/dev/null | head -c "$MAX_STDOUT_BYTES"
|
|
999
1321
|
fi
|
|
1322
|
+
|
|
1323
|
+
return "$exit_code"
|
|
1000
1324
|
}
|
|
1001
1325
|
|
|
1002
1326
|
main
|