triflux 9.3.0 → 9.4.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/bin/triflux.mjs +33 -217
- package/hub/pipe.mjs +1 -8
- package/hub/team/psmux.mjs +21 -1
- package/hub/workers/claude-worker.mjs +1 -24
- package/hub/workers/codex-mcp.mjs +0 -4
- package/hub/workers/gemini-worker.mjs +108 -28
- package/hub/workers/interface.mjs +0 -1
- package/hub/workers/worker-utils.mjs +26 -0
- package/package.json +1 -1
- package/scripts/__tests__/remote-spawn.test.mjs +17 -3
- package/scripts/cross-review-gate.mjs +11 -65
- package/scripts/cross-review-tracker.mjs +10 -51
- package/scripts/headless-guard.mjs +5 -17
- package/scripts/lib/cross-review-utils.mjs +51 -0
- package/scripts/lib/hook-utils.mjs +14 -0
- package/scripts/lib/mcp-filter.mjs +10 -1
- package/scripts/psmux-safety-guard.mjs +64 -0
- package/scripts/remote-spawn.mjs +77 -23
- package/scripts/session-spawn-helper.mjs +2 -1
- package/scripts/setup.mjs +36 -6
- package/scripts/tfx-route.sh +93 -10
- package/skills/tfx-psmux-rules/SKILL.md +100 -13
- package/skills/tfx-research/SKILL.md +1 -1
- package/skills/tfx-setup/SKILL.md +17 -1
package/scripts/tfx-route.sh
CHANGED
|
@@ -617,12 +617,23 @@ codex_gte() {
|
|
|
617
617
|
_GEMINI_PROFILE_CACHE=""
|
|
618
618
|
resolve_gemini_profile() {
|
|
619
619
|
local profile="$1"
|
|
620
|
+
if [[ "$profile" == gemini-* ]]; then
|
|
621
|
+
echo "$profile"
|
|
622
|
+
return
|
|
623
|
+
fi
|
|
620
624
|
if [[ -z "$_GEMINI_PROFILE_CACHE" && -f "$GEMINI_PROFILES_PATH" ]]; then
|
|
621
625
|
_GEMINI_PROFILE_CACHE=$(cat "$GEMINI_PROFILES_PATH" 2>/dev/null || echo "{}")
|
|
622
626
|
fi
|
|
627
|
+
local settings_path="${HOME}/.gemini/settings.json"
|
|
628
|
+
local settings_cache="{}"
|
|
629
|
+
if [[ -f "$settings_path" ]]; then
|
|
630
|
+
settings_cache=$(cat "$settings_path" 2>/dev/null || echo "{}")
|
|
631
|
+
fi
|
|
623
632
|
local result
|
|
624
633
|
result=$("$NODE_BIN" -e "
|
|
625
634
|
const name = process.argv[1];
|
|
635
|
+
const primaryRaw = process.argv[2] || '{}';
|
|
636
|
+
const settingsRaw = process.argv[3] || '{}';
|
|
626
637
|
const defaults = {
|
|
627
638
|
pro31: 'gemini-3.1-pro-preview',
|
|
628
639
|
flash3: 'gemini-3-flash-preview',
|
|
@@ -630,13 +641,74 @@ resolve_gemini_profile() {
|
|
|
630
641
|
flash25: 'gemini-2.5-flash',
|
|
631
642
|
lite25: 'gemini-2.5-flash-lite'
|
|
632
643
|
};
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
|
|
644
|
+
|
|
645
|
+
if (typeof name === 'string' && name.startsWith('gemini-')) {
|
|
646
|
+
process.stdout.write(name);
|
|
647
|
+
process.exit(0);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
const parseJson = (raw) => {
|
|
651
|
+
try {
|
|
652
|
+
const parsed = JSON.parse(raw);
|
|
653
|
+
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
654
|
+
} catch {
|
|
655
|
+
return {};
|
|
656
|
+
}
|
|
657
|
+
};
|
|
658
|
+
|
|
659
|
+
const getModelValue = (entry) => {
|
|
660
|
+
if (!entry) return '';
|
|
661
|
+
if (typeof entry === 'string') return entry;
|
|
662
|
+
if (typeof entry !== 'object') return '';
|
|
663
|
+
if (typeof entry.model === 'string') return entry.model;
|
|
664
|
+
if (typeof entry.name === 'string' && entry.name.startsWith('gemini-')) return entry.name;
|
|
665
|
+
if (entry.model && typeof entry.model.name === 'string') return entry.model.name;
|
|
666
|
+
return '';
|
|
667
|
+
};
|
|
668
|
+
|
|
669
|
+
const getProfileBuckets = (cfg) => {
|
|
670
|
+
const buckets = [];
|
|
671
|
+
if (cfg.profiles && typeof cfg.profiles === 'object') buckets.push(cfg.profiles);
|
|
672
|
+
if (cfg.model?.profiles && typeof cfg.model.profiles === 'object') buckets.push(cfg.model.profiles);
|
|
673
|
+
if (cfg.modelProfiles && typeof cfg.modelProfiles === 'object') buckets.push(cfg.modelProfiles);
|
|
674
|
+
if (cfg.models && typeof cfg.models === 'object') buckets.push(cfg.models);
|
|
675
|
+
return buckets;
|
|
676
|
+
};
|
|
677
|
+
|
|
678
|
+
const getDefaultModel = (cfg) => {
|
|
679
|
+
return (
|
|
680
|
+
(typeof cfg.defaultModel === 'string' && cfg.defaultModel) ||
|
|
681
|
+
(typeof cfg.default_profile === 'string' && cfg.default_profile) ||
|
|
682
|
+
(typeof cfg.defaultProfile === 'string' && cfg.defaultProfile) ||
|
|
683
|
+
(typeof cfg.model === 'string' && cfg.model) ||
|
|
684
|
+
(typeof cfg.model?.default === 'string' && cfg.model.default) ||
|
|
685
|
+
''
|
|
686
|
+
);
|
|
687
|
+
};
|
|
688
|
+
|
|
689
|
+
const sources = [parseJson(primaryRaw), parseJson(settingsRaw)];
|
|
690
|
+
for (const cfg of sources) {
|
|
691
|
+
for (const bucket of getProfileBuckets(cfg)) {
|
|
692
|
+
const value = getModelValue(bucket[name]);
|
|
693
|
+
if (value) {
|
|
694
|
+
process.stdout.write(value);
|
|
695
|
+
process.exit(0);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
if (name === 'default') {
|
|
701
|
+
for (const cfg of sources) {
|
|
702
|
+
const value = getDefaultModel(cfg);
|
|
703
|
+
if (value) {
|
|
704
|
+
process.stdout.write(value);
|
|
705
|
+
process.exit(0);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
process.stdout.write(defaults[name] || defaults.pro31);
|
|
711
|
+
" "$profile" "$_GEMINI_PROFILE_CACHE" "$settings_cache" 2>/dev/null)
|
|
640
712
|
echo "${result:-gemini-3.1-pro-preview}"
|
|
641
713
|
}
|
|
642
714
|
|
|
@@ -853,6 +925,7 @@ CODEX_MCP_TRANSPORT_EXIT_CODE=70
|
|
|
853
925
|
|
|
854
926
|
apply_cli_mode() {
|
|
855
927
|
local codex_base="--dangerously-bypass-approvals-and-sandbox --skip-git-repo-check"
|
|
928
|
+
local gemini_tier=""
|
|
856
929
|
|
|
857
930
|
case "$TFX_CLI_MODE" in
|
|
858
931
|
codex)
|
|
@@ -894,7 +967,12 @@ apply_cli_mode() {
|
|
|
894
967
|
*)
|
|
895
968
|
CLI_ARGS="-m $(resolve_gemini_profile flash3) -y --prompt"; CLI_EFFORT="flash3" ;;
|
|
896
969
|
esac
|
|
897
|
-
|
|
970
|
+
case "$CLI_EFFORT" in
|
|
971
|
+
pro*) gemini_tier="pro" ;;
|
|
972
|
+
flash*|lite*) gemini_tier="flash" ;;
|
|
973
|
+
*) gemini_tier="$CLI_EFFORT" ;;
|
|
974
|
+
esac
|
|
975
|
+
echo "[tfx-route] TFX_CLI_MODE=gemini: $AGENT_TYPE → gemini($gemini_tier)로 리매핑" >&2
|
|
898
976
|
fi ;;
|
|
899
977
|
auto)
|
|
900
978
|
if [[ "$CLI_TYPE" == "codex" ]] && ! command -v "$CODEX_BIN" &>/dev/null; then
|
|
@@ -1226,7 +1304,7 @@ _gemini_run_once() {
|
|
|
1226
1304
|
else
|
|
1227
1305
|
"$TIMEOUT_BIN" "$TIMEOUT_SEC" "$CLI_CMD" "${g_args[@]}" "$prompt" >"$STDOUT_LOG" 2>"$STDERR_LOG" &
|
|
1228
1306
|
fi
|
|
1229
|
-
|
|
1307
|
+
GEMINI_RUN_PID=$!
|
|
1230
1308
|
}
|
|
1231
1309
|
|
|
1232
1310
|
gemini_with_retry() {
|
|
@@ -1243,7 +1321,12 @@ gemini_with_retry() {
|
|
|
1243
1321
|
while (( attempt < max_retries )); do
|
|
1244
1322
|
exit_code_local=0
|
|
1245
1323
|
local pid
|
|
1246
|
-
|
|
1324
|
+
_gemini_run_once "$use_tee_flag" "$prompt" "${g_args[@]}"
|
|
1325
|
+
pid="${GEMINI_RUN_PID:-}"
|
|
1326
|
+
if [[ -z "$pid" ]]; then
|
|
1327
|
+
echo "[tfx-route] Gemini: worker pid 획득 실패" >&2
|
|
1328
|
+
return 1
|
|
1329
|
+
fi
|
|
1247
1330
|
|
|
1248
1331
|
local health_ok=true
|
|
1249
1332
|
local intervals=(1 2 3 5 8)
|
|
@@ -153,34 +153,121 @@ Get-Content 'C:\path\prompts\prompt.md' -Raw | codex
|
|
|
153
153
|
|
|
154
154
|
---
|
|
155
155
|
|
|
156
|
-
## RULE 5: WT
|
|
156
|
+
## RULE 5: WT 세션 정리 — CRITICAL (프리징 치명 버그)
|
|
157
157
|
|
|
158
|
-
|
|
158
|
+
> **이 규칙을 위반하면 Windows Terminal이 완전히 프리징된다.**
|
|
159
|
+
> 복구 방법은 작업 관리자에서 WindowsTerminal.exe 강제 종료뿐이다.
|
|
160
|
+
> **모든 열린 탭/세션이 유실된다.** 절대 위반하지 마라.
|
|
159
161
|
|
|
160
|
-
###
|
|
162
|
+
### 프리징 원인
|
|
163
|
+
|
|
164
|
+
WT 패인이 `psmux attach-session`으로 세션에 연결된 상태에서
|
|
165
|
+
`psmux kill-session`을 직접 호출하면 WT의 PTY 핸들이 dangling 상태가 되어
|
|
166
|
+
**전체 WT 프로세스가 응답 불능**이 된다.
|
|
167
|
+
|
|
168
|
+
### MUST — 3단계 정리 (순서 반드시 준수)
|
|
161
169
|
|
|
162
170
|
```bash
|
|
163
|
-
# 1) graceful exit
|
|
164
|
-
for s in $(psmux list-sessions -F '#{session_name}' 2>/dev/null | grep
|
|
171
|
+
# 1) graceful exit — 각 세션 내 프로세스를 정상 종료
|
|
172
|
+
for s in $(psmux list-sessions -F '#{session_name}' 2>/dev/null | grep "$PREFIX"); do
|
|
165
173
|
psmux send-keys -t "$s" "exit" Enter 2>/dev/null || true
|
|
166
174
|
done
|
|
167
175
|
|
|
168
|
-
# 2) WT 패인
|
|
176
|
+
# 2) WT 패인 분리 대기 — exit이 WT에 전파되는 시간 필요
|
|
169
177
|
sleep 2
|
|
170
178
|
|
|
171
|
-
# 3) 잔여 세션 강제 종료
|
|
172
|
-
for s in $(psmux list-sessions -F '#{session_name}' 2>/dev/null | grep
|
|
179
|
+
# 3) 잔여 세션 강제 종료 — exit이 먹히지 않은 경우만
|
|
180
|
+
for s in $(psmux list-sessions -F '#{session_name}' 2>/dev/null | grep "$PREFIX"); do
|
|
173
181
|
psmux kill-session -t "$s" 2>/dev/null || true
|
|
174
182
|
done
|
|
175
183
|
```
|
|
176
184
|
|
|
177
|
-
### MUST NOT
|
|
185
|
+
### MUST NOT — 아래 패턴은 전부 프리징 유발
|
|
178
186
|
|
|
179
187
|
```bash
|
|
180
|
-
# WRONG —
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
188
|
+
# WRONG 1 — exit 없이 바로 kill
|
|
189
|
+
psmux kill-session -t "$session"
|
|
190
|
+
|
|
191
|
+
# WRONG 2 — sleep 없이 exit 직후 kill
|
|
192
|
+
psmux send-keys -t "$s" "exit" Enter
|
|
193
|
+
psmux kill-session -t "$s" # ← WT가 아직 PTY를 잡고 있음
|
|
194
|
+
|
|
195
|
+
# WRONG 3 — detach 직후 kill
|
|
196
|
+
psmux send-keys -t "$s" "psmux detach" Enter
|
|
197
|
+
psmux kill-session -t "$s" # ← detach 완료 전에 kill → 프리징
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### WT 패인 직접 닫기 (세션이 아닌 WT 쪽에서 정리)
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
# WT 패인을 닫는 것이 세션 kill보다 안전
|
|
204
|
+
wt.exe -w 0 close-pane # 현재 포커스된 패인 닫기
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## RULE 5-1: psmux 경로 탐색
|
|
210
|
+
|
|
211
|
+
psmux는 환경마다 설치 위치가 다르다. `hub/team/psmux.mjs`의 `PSMUX_BIN`이
|
|
212
|
+
자동 탐색하지만, 스크립트에서 직접 psmux를 호출할 때도 경로를 고려해야 한다.
|
|
213
|
+
|
|
214
|
+
탐색 우선순위:
|
|
215
|
+
1. `$PSMUX_BIN` 환경변수 (설정 시 최우선)
|
|
216
|
+
2. PATH의 `psmux`
|
|
217
|
+
3. `%LOCALAPPDATA%\psmux\psmux.exe` (Windows 기본)
|
|
218
|
+
4. `%APPDATA%\npm\psmux.cmd` (npm global)
|
|
219
|
+
5. `~\scoop\shims\psmux.exe` (Scoop)
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## RULE 5-2: WT 명령 치트시트
|
|
224
|
+
|
|
225
|
+
### 패인 분할
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
# 현재 창에서 수평 분할 (상/하)
|
|
229
|
+
wt.exe -w 0 sp -H -p triflux --title "worker" psmux attach-session -t SESSION
|
|
230
|
+
|
|
231
|
+
# 현재 창에서 수직 분할 (좌/우)
|
|
232
|
+
wt.exe -w 0 sp -V -p triflux --title "worker" psmux attach-session -t SESSION
|
|
233
|
+
|
|
234
|
+
# 2x2 그리드 (4 패인)
|
|
235
|
+
wt.exe -w 0 \
|
|
236
|
+
sp -H -p triflux --title "w1" psmux attach-session -t S1 \; \
|
|
237
|
+
sp -V -p triflux --title "w2" psmux attach-session -t S2 \; \
|
|
238
|
+
move-focus up \; \
|
|
239
|
+
sp -V -p triflux --title "w3" psmux attach-session -t S3
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### 포커스 이동
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
wt.exe -w 0 move-focus up|down|left|right
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### 패인 닫기 (세션 kill보다 안전)
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
wt.exe -w 0 close-pane
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### 필수 옵션
|
|
255
|
+
|
|
256
|
+
| 옵션 | 의미 | 필수 여부 |
|
|
257
|
+
|------|------|----------|
|
|
258
|
+
| `-w 0` | 현재 WT 윈도우 | 필수 (없으면 새 창) |
|
|
259
|
+
| `-p triflux` | triflux WT 프로파일 | 필수 (테마/셸 일관성) |
|
|
260
|
+
| `--title "name"` | 패인 제목 | 권장 (식별용) |
|
|
261
|
+
| `sp -H` / `sp -V` | 분할 방향 | 필수 |
|
|
262
|
+
|
|
263
|
+
### 새 탭 금지
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
# WRONG — 새 탭 생성 (nt)
|
|
267
|
+
wt.exe -w 0 nt -p triflux ...
|
|
268
|
+
|
|
269
|
+
# RIGHT — split-pane (sp)
|
|
270
|
+
wt.exe -w 0 sp -V -p triflux ...
|
|
184
271
|
```
|
|
185
272
|
|
|
186
273
|
---
|
|
@@ -57,7 +57,7 @@ AskUserQuestion:
|
|
|
57
57
|
최적화된 쿼리를 Gemini CLI로 전달한다. Gemini는 네이티브 Google Search를 사용해 검색과 요약을 모두 수행한다:
|
|
58
58
|
|
|
59
59
|
```
|
|
60
|
-
Bash("bash scripts/tfx-route.sh gemini
|
|
60
|
+
Bash("bash scripts/tfx-route.sh gemini 'Research the following topic. Use Google Search to find current information. Return a structured markdown summary with sources: {optimized_query}' auto 120")
|
|
61
61
|
```
|
|
62
62
|
|
|
63
63
|
Gemini가 반환하는 결과에는 검색 결과, 출처 URL, 핵심 요약이 포함된다. Claude는 이 단계에서 토큰을 소비하지 않는다.
|
|
@@ -11,7 +11,10 @@ argument-hint: "[doctor]"
|
|
|
11
11
|
|
|
12
12
|
# tfx-setup — triflux 초기 설정 위저드
|
|
13
13
|
|
|
14
|
-
> 설치 후 최초 1회
|
|
14
|
+
> 설치 후 최초 1회 + **매 업데이트 후** 실행 권장.
|
|
15
|
+
> `tfx update` 완료 후에도 이 스킬의 단계 1.5(훅 등록 확인)를 반드시 실행하여
|
|
16
|
+
> 신규/변경된 훅이 settings.json에 반영되도록 한다.
|
|
17
|
+
> 훅 우선순위 관리는 `tfx-hooks` 스킬의 오케스트레이터 패턴을 따른다.
|
|
15
18
|
|
|
16
19
|
## 워크플로우
|
|
17
20
|
|
|
@@ -43,6 +46,19 @@ Bash("triflux setup")
|
|
|
43
46
|
|
|
44
47
|
스크립트/HUD/스킬을 `~/.claude/`에 배포. 결과 표시.
|
|
45
48
|
|
|
49
|
+
#### 단계 1.5: 훅 등록 확인
|
|
50
|
+
|
|
51
|
+
`~/.claude/settings.json`을 Read 도구로 읽어 필수 훅이 등록되어 있는지 확인한다.
|
|
52
|
+
|
|
53
|
+
필수 훅 목록:
|
|
54
|
+
| 이벤트 | matcher | 스크립트 | 역할 |
|
|
55
|
+
|--------|---------|---------|------|
|
|
56
|
+
| PreToolUse | Bash\|Agent | headless-guard-fast.sh | Codex/Gemini 직접 호출 차단 |
|
|
57
|
+
| PreToolUse | Bash | psmux-safety-guard.mjs | psmux kill-session 직접 호출 차단 (WT 프리징 방지) |
|
|
58
|
+
| PreToolUse | Skill | tfx-gate-activate.mjs | tfx-multi 게이트 |
|
|
59
|
+
|
|
60
|
+
누락된 훅이 있으면 update-config 스킬로 등록한다. 이미 있으면 ✅ 표시.
|
|
61
|
+
|
|
46
62
|
#### 단계 2: HUD 설정
|
|
47
63
|
|
|
48
64
|
`~/.claude/settings.json`을 Read 도구로 읽어 `statusLine` 확인.
|