triflux 7.3.2 → 7.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/README.ko.md +145 -145
- package/README.md +145 -145
- package/hub/pipeline/index.mjs +318 -318
- package/hub/schema.sql +146 -146
- package/hub/team/agent-map.json +2 -1
- package/hub/team/backend.mjs +3 -3
- package/hub/team/cli/commands/kill.mjs +37 -37
- package/hub/team/cli/commands/start/parse-args.mjs +4 -2
- package/hub/team/cli/commands/stop.mjs +31 -31
- package/hub/team/cli/commands/task.mjs +30 -30
- package/hub/team/cli/services/hub-client.mjs +208 -208
- package/hub/team/cli/services/native-control.mjs +4 -1
- package/hub/team/cli/services/runtime-mode.mjs +62 -62
- package/hub/team/cli/services/state-store.mjs +48 -48
- package/hub/team/codex-compat.mjs +78 -0
- package/hub/team/dashboard.mjs +274 -274
- package/hub/team/native.mjs +649 -649
- package/hub/team/pane.mjs +154 -150
- package/hub/team/psmux.mjs +1041 -1023
- package/hub/team/tui-viewer.mjs +2 -2
- package/hub/team/tui.mjs +12 -1
- package/hub/tools.mjs +554 -554
- package/hud/constants.mjs +3 -0
- package/package.json +1 -1
- package/scripts/claude-logged.ps1 +54 -0
- package/scripts/headless-guard.mjs +94 -7
- package/scripts/lib/mcp-filter.mjs +720 -720
- package/scripts/preflight-cache.mjs +137 -137
- package/scripts/remote-spawn.mjs +222 -0
- package/scripts/setup.mjs +84 -1
- package/scripts/tfx-gate-activate.mjs +89 -0
- package/scripts/tfx-route-post.mjs +17 -13
- package/scripts/tfx-route.sh +118 -46
- package/scripts/token-snapshot.mjs +575 -575
- package/skills/remote-spawn/SKILL.md +63 -0
- package/skills/tfx-auto/SKILL.md +1 -1
- package/skills/tfx-multi/SKILL.md +1 -1
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* tfx-multi-activate.mjs — PreToolUse(Skill) 훅
|
|
4
|
+
*
|
|
5
|
+
* /tfx-multi 스킬 호출을 감지하여 상태 파일을 설정한다.
|
|
6
|
+
* headless-guard.mjs가 이 상태를 읽어 A(gate) + B(nudge)를 수행.
|
|
7
|
+
*
|
|
8
|
+
* 상태 파일: $TMPDIR/tfx-multi-state.json
|
|
9
|
+
* 자동 만료: 30분
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { writeFileSync, existsSync, readFileSync } from "node:fs";
|
|
13
|
+
import { tmpdir } from "node:os";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
|
|
16
|
+
const STATE_FILE = join(tmpdir(), "tfx-multi-state.json");
|
|
17
|
+
const EXPIRE_MS = 30 * 60 * 1000; // 30분
|
|
18
|
+
|
|
19
|
+
async function main() {
|
|
20
|
+
let raw = "";
|
|
21
|
+
for await (const chunk of process.stdin) raw += chunk;
|
|
22
|
+
|
|
23
|
+
if (!raw.trim()) {
|
|
24
|
+
process.exit(0);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let input;
|
|
28
|
+
try {
|
|
29
|
+
input = JSON.parse(raw);
|
|
30
|
+
} catch {
|
|
31
|
+
process.exit(0);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const toolName = input.tool_name || "";
|
|
35
|
+
const toolInput = input.tool_input || {};
|
|
36
|
+
|
|
37
|
+
if (toolName !== "Skill") {
|
|
38
|
+
process.exit(0);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const skill = (toolInput.skill || "").toLowerCase();
|
|
42
|
+
|
|
43
|
+
// 모든 tfx CLI 라우팅 스킬에 gate 적용
|
|
44
|
+
const TFX_ROUTING_SKILLS = new Set([
|
|
45
|
+
"tfx-multi", "tfx-team", "tfx-auto", "tfx-auto-codex",
|
|
46
|
+
"tfx-codex", "tfx-gemini", "tfx-autoresearch",
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
if (TFX_ROUTING_SKILLS.has(skill)) {
|
|
50
|
+
// 활성화: 상태 파일 생성/갱신
|
|
51
|
+
const state = {
|
|
52
|
+
active: true,
|
|
53
|
+
activatedAt: Date.now(),
|
|
54
|
+
dispatched: false,
|
|
55
|
+
nativeWorkCalls: 0,
|
|
56
|
+
nativeWorkCallsSinceDispatch: 0,
|
|
57
|
+
};
|
|
58
|
+
writeFileSync(STATE_FILE, JSON.stringify(state));
|
|
59
|
+
|
|
60
|
+
// additionalContext로 Lead에게 알림
|
|
61
|
+
process.stdout.write(
|
|
62
|
+
JSON.stringify({
|
|
63
|
+
hookSpecificOutput: {
|
|
64
|
+
hookEventName: "PreToolUse",
|
|
65
|
+
additionalContext:
|
|
66
|
+
"[tfx-multi] gate 활성화됨. CLI 작업은 headless로 dispatch 필수:\n" +
|
|
67
|
+
'Bash("tfx multi --teammate-mode headless --auto-attach --dashboard --assign \'codex:프롬프트:역할\' --timeout 600")',
|
|
68
|
+
},
|
|
69
|
+
}),
|
|
70
|
+
);
|
|
71
|
+
process.exit(0);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// /tfx-multi 외 스킬 호출 시: 기존 상태 만료 체크만
|
|
75
|
+
if (existsSync(STATE_FILE)) {
|
|
76
|
+
try {
|
|
77
|
+
const state = JSON.parse(readFileSync(STATE_FILE, "utf8"));
|
|
78
|
+
if (Date.now() - state.activatedAt > EXPIRE_MS) {
|
|
79
|
+
// 만료 → 삭제하지 않고 headless-guard가 처리
|
|
80
|
+
}
|
|
81
|
+
} catch {
|
|
82
|
+
/* ignore */
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
process.exit(0);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
main().catch(() => process.exit(0));
|
|
@@ -264,19 +264,23 @@ function trackCliIssue(cliType, agent, stderrText, exitCode) {
|
|
|
264
264
|
|
|
265
265
|
const snippet = stderrText.substring(0, 200).replace(/\n/g, " ");
|
|
266
266
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
267
|
+
const retryCount = (matched.pattern === "rate_limit" && cliType === "gemini")
|
|
268
|
+
? parseInt(process.env.TFX_GEMINI_429_RETRIES || "0")
|
|
269
|
+
: undefined;
|
|
270
|
+
|
|
271
|
+
const issueEntry = {
|
|
272
|
+
ts: Date.now(),
|
|
273
|
+
cli: cliType,
|
|
274
|
+
agent,
|
|
275
|
+
pattern: matched.pattern,
|
|
276
|
+
msg: matched.msg,
|
|
277
|
+
severity: matched.severity,
|
|
278
|
+
snippet,
|
|
279
|
+
resolved: false,
|
|
280
|
+
};
|
|
281
|
+
if (retryCount !== undefined) issueEntry.retry_count = retryCount;
|
|
282
|
+
|
|
283
|
+
appendFileSync(issuesFile, JSON.stringify(issueEntry) + "\n");
|
|
280
284
|
|
|
281
285
|
// 자동 회전
|
|
282
286
|
const content = readFileSync(issuesFile, "utf-8").trim();
|
package/scripts/tfx-route.sh
CHANGED
|
@@ -566,12 +566,31 @@ capture_workspace_signature() {
|
|
|
566
566
|
git status --short --untracked-files=all --ignore-submodules=all 2>/dev/null || return 1
|
|
567
567
|
}
|
|
568
568
|
|
|
569
|
+
# ── Codex CLI 버전 감지 (캐시) ──
|
|
570
|
+
_CODEX_VERSION=""
|
|
571
|
+
get_codex_version() {
|
|
572
|
+
if [[ -n "$_CODEX_VERSION" ]]; then echo "$_CODEX_VERSION"; return; fi
|
|
573
|
+
local raw
|
|
574
|
+
raw=$("$CODEX_BIN" --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
|
|
575
|
+
_CODEX_VERSION="${raw:-0.0.0}"
|
|
576
|
+
echo "$_CODEX_VERSION"
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
# codex_gte <min_version>: 현재 버전이 min 이상이면 true(0), 아니면 false(1)
|
|
580
|
+
codex_gte() {
|
|
581
|
+
local min="$1"
|
|
582
|
+
local cur
|
|
583
|
+
cur=$(get_codex_version)
|
|
584
|
+
printf '%s\n%s' "$min" "$cur" | sort -V | head -1 | grep -q "^${min}$"
|
|
585
|
+
}
|
|
586
|
+
|
|
569
587
|
# ── 라우팅 테이블 ──
|
|
570
588
|
# CLI_TYPE/CLI_CMD: agent-map.json 단일 소스. 상세 설정: 아래 case 문.
|
|
571
589
|
# 반환: CLI_TYPE, CLI_CMD, CLI_ARGS, CLI_EFFORT, DEFAULT_TIMEOUT, RUN_MODE, OPUS_OVERSIGHT
|
|
572
590
|
route_agent() {
|
|
573
591
|
local agent="$1"
|
|
574
592
|
local codex_base="--dangerously-bypass-approvals-and-sandbox --skip-git-repo-check"
|
|
593
|
+
echo "[tfx-route] Codex 버전: $(get_codex_version)" >&2
|
|
575
594
|
local map_file
|
|
576
595
|
map_file="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../hub/team/agent-map.json"
|
|
577
596
|
# ── breadcrumb 폴백 (synced 환경: ~/.claude/scripts/) ──
|
|
@@ -669,13 +688,20 @@ route_agent() {
|
|
|
669
688
|
CLI_ARGS="-m gemini-3-flash-preview -y --prompt"
|
|
670
689
|
CLI_EFFORT="flash"; DEFAULT_TIMEOUT=900; RUN_MODE="bg"; OPUS_OVERSIGHT="false" ;;
|
|
671
690
|
|
|
672
|
-
# ───
|
|
673
|
-
explore
|
|
674
|
-
CLI_EFFORT="n/a"; DEFAULT_TIMEOUT=600; RUN_MODE="fg"; OPUS_OVERSIGHT="false"
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
691
|
+
# ─── 탐색 (Claude-native: Glob/Grep/Read 직접 접근) ───
|
|
692
|
+
explore)
|
|
693
|
+
CLI_EFFORT="n/a"; DEFAULT_TIMEOUT=600; RUN_MODE="fg"; OPUS_OVERSIGHT="false" ;;
|
|
694
|
+
|
|
695
|
+
# ─── 검증/테스트 (Codex: 무료 + 파일 쓰기 가능) ───
|
|
696
|
+
verifier)
|
|
697
|
+
CLI_ARGS="exec --profile thorough ${codex_base} review"
|
|
698
|
+
CLI_EFFORT="thorough"; DEFAULT_TIMEOUT=1200; RUN_MODE="fg"; OPUS_OVERSIGHT="false" ;;
|
|
699
|
+
test-engineer)
|
|
700
|
+
CLI_ARGS="exec ${codex_base}"
|
|
701
|
+
CLI_EFFORT="high"; DEFAULT_TIMEOUT=1200; RUN_MODE="bg"; OPUS_OVERSIGHT="false" ;;
|
|
702
|
+
qa-tester)
|
|
703
|
+
CLI_ARGS="exec --profile thorough ${codex_base} review"
|
|
704
|
+
CLI_EFFORT="thorough"; DEFAULT_TIMEOUT=1200; RUN_MODE="bg"; OPUS_OVERSIGHT="false" ;;
|
|
679
705
|
|
|
680
706
|
# ─── 경량 ───
|
|
681
707
|
spark)
|
|
@@ -1108,6 +1134,90 @@ run_stream_worker() {
|
|
|
1108
1134
|
return "$exit_code_local"
|
|
1109
1135
|
}
|
|
1110
1136
|
|
|
1137
|
+
# Gemini 429 지수 백오프 재시도 래퍼
|
|
1138
|
+
# 사용: gemini_with_retry <use_tee_flag> <gemini_args_array_name> <prompt>
|
|
1139
|
+
# 429/rate limit 감지 시 최대 3회 재시도 (2→4→8초 백오프)
|
|
1140
|
+
_gemini_run_once() {
|
|
1141
|
+
local use_tee_flag="$1"
|
|
1142
|
+
local prompt="$2"
|
|
1143
|
+
shift 2
|
|
1144
|
+
local -a g_args=("$@")
|
|
1145
|
+
|
|
1146
|
+
if [[ "$use_tee_flag" == "true" ]]; then
|
|
1147
|
+
timeout "$TIMEOUT_SEC" "$CLI_CMD" "${g_args[@]}" "$prompt" 2>"$STDERR_LOG" | tee "$STDOUT_LOG" &
|
|
1148
|
+
else
|
|
1149
|
+
timeout "$TIMEOUT_SEC" "$CLI_CMD" "${g_args[@]}" "$prompt" >"$STDOUT_LOG" 2>"$STDERR_LOG" &
|
|
1150
|
+
fi
|
|
1151
|
+
echo "$!"
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
gemini_with_retry() {
|
|
1155
|
+
local use_tee_flag="$1"
|
|
1156
|
+
local prompt="$2"
|
|
1157
|
+
shift 2
|
|
1158
|
+
local -a g_args=("$@")
|
|
1159
|
+
|
|
1160
|
+
local max_retries=3
|
|
1161
|
+
local attempt=0
|
|
1162
|
+
local delay=2
|
|
1163
|
+
local exit_code_local=0
|
|
1164
|
+
|
|
1165
|
+
while (( attempt < max_retries )); do
|
|
1166
|
+
exit_code_local=0
|
|
1167
|
+
local pid
|
|
1168
|
+
pid=$(_gemini_run_once "$use_tee_flag" "$prompt" "${g_args[@]}")
|
|
1169
|
+
|
|
1170
|
+
local health_ok=true
|
|
1171
|
+
local intervals=(1 2 3 5 8)
|
|
1172
|
+
for wait_sec in "${intervals[@]}"; do
|
|
1173
|
+
sleep "$wait_sec"
|
|
1174
|
+
if [[ -s "$STDOUT_LOG" ]] || [[ -s "$STDERR_LOG" ]]; then
|
|
1175
|
+
break
|
|
1176
|
+
fi
|
|
1177
|
+
if ! kill -0 "$pid" 2>/dev/null; then
|
|
1178
|
+
health_ok=false
|
|
1179
|
+
echo "[tfx-route] Gemini: 출력 없이 프로세스 종료 (${wait_sec}초 체크)" >&2
|
|
1180
|
+
break
|
|
1181
|
+
fi
|
|
1182
|
+
done
|
|
1183
|
+
|
|
1184
|
+
local hb_pid
|
|
1185
|
+
if [[ "$health_ok" == "false" ]]; then
|
|
1186
|
+
wait "$pid" 2>/dev/null
|
|
1187
|
+
else
|
|
1188
|
+
heartbeat_monitor "$pid" &
|
|
1189
|
+
hb_pid=$!
|
|
1190
|
+
wait "$pid" || exit_code_local=$?
|
|
1191
|
+
kill "$hb_pid" 2>/dev/null; wait "$hb_pid" 2>/dev/null
|
|
1192
|
+
fi
|
|
1193
|
+
|
|
1194
|
+
# 성공 시 즉시 반환
|
|
1195
|
+
if [[ $exit_code_local -eq 0 ]]; then
|
|
1196
|
+
return 0
|
|
1197
|
+
fi
|
|
1198
|
+
|
|
1199
|
+
# 429 / rate limit 감지
|
|
1200
|
+
if grep -qiE '429|rate.limit|too many requests' "$STDERR_LOG" 2>/dev/null; then
|
|
1201
|
+
attempt=$(( attempt + 1 ))
|
|
1202
|
+
if (( attempt < max_retries )); then
|
|
1203
|
+
echo "[tfx-route] Gemini 429 감지. ${delay}초 후 재시도 ($attempt/$max_retries)..." >&2
|
|
1204
|
+
sleep "$delay"
|
|
1205
|
+
delay=$(( delay * 2 ))
|
|
1206
|
+
: > "$STDOUT_LOG"
|
|
1207
|
+
: > "$STDERR_LOG"
|
|
1208
|
+
continue
|
|
1209
|
+
else
|
|
1210
|
+
echo "[tfx-route] Gemini 429: ${max_retries}회 재시도 실패" >&2
|
|
1211
|
+
fi
|
|
1212
|
+
fi
|
|
1213
|
+
|
|
1214
|
+
# 비-429 에러 또는 최대 재시도 초과 시 즉시 반환
|
|
1215
|
+
return "$exit_code_local"
|
|
1216
|
+
done
|
|
1217
|
+
|
|
1218
|
+
return "$exit_code_local"
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1111
1221
|
run_legacy_gemini() {
|
|
1112
1222
|
local prompt="$1"
|
|
1113
1223
|
local use_tee_flag="$2"
|
|
@@ -1133,45 +1243,7 @@ run_legacy_gemini() {
|
|
|
1133
1243
|
fi
|
|
1134
1244
|
fi
|
|
1135
1245
|
|
|
1136
|
-
|
|
1137
|
-
timeout "$TIMEOUT_SEC" "$CLI_CMD" "${gemini_args[@]}" "$prompt" 2>"$STDERR_LOG" | tee "$STDOUT_LOG" &
|
|
1138
|
-
else
|
|
1139
|
-
timeout "$TIMEOUT_SEC" "$CLI_CMD" "${gemini_args[@]}" "$prompt" >"$STDOUT_LOG" 2>"$STDERR_LOG" &
|
|
1140
|
-
fi
|
|
1141
|
-
local pid=$!
|
|
1142
|
-
|
|
1143
|
-
local health_ok=true
|
|
1144
|
-
local intervals=(1 2 3 5 8)
|
|
1145
|
-
for wait_sec in "${intervals[@]}"; do
|
|
1146
|
-
sleep "$wait_sec"
|
|
1147
|
-
if [[ -s "$STDOUT_LOG" ]] || [[ -s "$STDERR_LOG" ]]; then
|
|
1148
|
-
break
|
|
1149
|
-
fi
|
|
1150
|
-
if ! kill -0 "$pid" 2>/dev/null; then
|
|
1151
|
-
health_ok=false
|
|
1152
|
-
echo "[tfx-route] Gemini: 출력 없이 프로세스 종료 (${wait_sec}초 체크)" >&2
|
|
1153
|
-
break
|
|
1154
|
-
fi
|
|
1155
|
-
done
|
|
1156
|
-
|
|
1157
|
-
local exit_code_local=0
|
|
1158
|
-
local hb_pid
|
|
1159
|
-
if [[ "$health_ok" == "false" ]]; then
|
|
1160
|
-
wait "$pid" 2>/dev/null
|
|
1161
|
-
echo "[tfx-route] Gemini crash 감지, 재시도 중..." >&2
|
|
1162
|
-
if [[ "$use_tee_flag" == "true" ]]; then
|
|
1163
|
-
timeout "$TIMEOUT_SEC" "$CLI_CMD" "${gemini_args[@]}" "$prompt" 2>"$STDERR_LOG" | tee "$STDOUT_LOG" &
|
|
1164
|
-
else
|
|
1165
|
-
timeout "$TIMEOUT_SEC" "$CLI_CMD" "${gemini_args[@]}" "$prompt" >"$STDOUT_LOG" 2>"$STDERR_LOG" &
|
|
1166
|
-
fi
|
|
1167
|
-
pid=$!
|
|
1168
|
-
fi
|
|
1169
|
-
|
|
1170
|
-
heartbeat_monitor "$pid" &
|
|
1171
|
-
hb_pid=$!
|
|
1172
|
-
wait "$pid" || exit_code_local=$?
|
|
1173
|
-
kill "$hb_pid" 2>/dev/null; wait "$hb_pid" 2>/dev/null
|
|
1174
|
-
return "$exit_code_local"
|
|
1246
|
+
gemini_with_retry "$use_tee_flag" "$prompt" "${gemini_args[@]}"
|
|
1175
1247
|
}
|
|
1176
1248
|
|
|
1177
1249
|
resolve_codex_mcp_script() {
|