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.
Files changed (38) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.ko.md +145 -145
  3. package/README.md +145 -145
  4. package/hub/pipeline/index.mjs +318 -318
  5. package/hub/schema.sql +146 -146
  6. package/hub/team/agent-map.json +2 -1
  7. package/hub/team/backend.mjs +3 -3
  8. package/hub/team/cli/commands/kill.mjs +37 -37
  9. package/hub/team/cli/commands/start/parse-args.mjs +4 -2
  10. package/hub/team/cli/commands/stop.mjs +31 -31
  11. package/hub/team/cli/commands/task.mjs +30 -30
  12. package/hub/team/cli/services/hub-client.mjs +208 -208
  13. package/hub/team/cli/services/native-control.mjs +4 -1
  14. package/hub/team/cli/services/runtime-mode.mjs +62 -62
  15. package/hub/team/cli/services/state-store.mjs +48 -48
  16. package/hub/team/codex-compat.mjs +78 -0
  17. package/hub/team/dashboard.mjs +274 -274
  18. package/hub/team/native.mjs +649 -649
  19. package/hub/team/pane.mjs +154 -150
  20. package/hub/team/psmux.mjs +1041 -1023
  21. package/hub/team/tui-viewer.mjs +2 -2
  22. package/hub/team/tui.mjs +12 -1
  23. package/hub/tools.mjs +554 -554
  24. package/hud/constants.mjs +3 -0
  25. package/package.json +1 -1
  26. package/scripts/claude-logged.ps1 +54 -0
  27. package/scripts/headless-guard.mjs +94 -7
  28. package/scripts/lib/mcp-filter.mjs +720 -720
  29. package/scripts/preflight-cache.mjs +137 -137
  30. package/scripts/remote-spawn.mjs +222 -0
  31. package/scripts/setup.mjs +84 -1
  32. package/scripts/tfx-gate-activate.mjs +89 -0
  33. package/scripts/tfx-route-post.mjs +17 -13
  34. package/scripts/tfx-route.sh +118 -46
  35. package/scripts/token-snapshot.mjs +575 -575
  36. package/skills/remote-spawn/SKILL.md +63 -0
  37. package/skills/tfx-auto/SKILL.md +1 -1
  38. 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
- appendFileSync(
268
- issuesFile,
269
- JSON.stringify({
270
- ts: Date.now(),
271
- cli: cliType,
272
- agent,
273
- pattern: matched.pattern,
274
- msg: matched.msg,
275
- severity: matched.severity,
276
- snippet,
277
- resolved: false,
278
- }) + "\n",
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();
@@ -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
- # ─── 탐색/검증/테스트 (Claude-native 우선, TFX_NO_CLAUDE_NATIVE=1일 때만 Codex 리매핑) ───
673
- explore|verifier|test-engineer|qa-tester)
674
- CLI_EFFORT="n/a"; DEFAULT_TIMEOUT=600; RUN_MODE="fg"; OPUS_OVERSIGHT="false"
675
- case "$agent" in
676
- test-engineer|qa-tester) DEFAULT_TIMEOUT=1200; RUN_MODE="bg" ;;
677
- esac
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
- if [[ "$use_tee_flag" == "true" ]]; then
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() {