triflux 10.13.1 → 10.13.3
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/marketplace.json +34 -0
- package/.claude-plugin/plugin.json +22 -0
- package/bin/triflux.mjs +123 -13
- package/config/mcp-registry.json +29 -0
- package/package.json +64 -21
- package/scripts/__tests__/setup-cleanup-stale-skills.test.mjs +105 -0
- package/scripts/setup.mjs +7 -8
- package/scripts/tfx-route-worker.mjs +1 -1
- package/scripts/tfx-route.sh +69 -3
- package/tui/codex-profile.mjs +457 -0
- package/tui/core.mjs +266 -0
- package/tui/doctor.mjs +375 -0
- package/tui/gemini-profile.mjs +299 -0
- package/tui/monitor-data.mjs +152 -0
- package/tui/monitor.mjs +333 -0
- package/tui/setup.mjs +599 -0
- package/CLAUDE.md +0 -161
- package/references/cli-parameter-reference.md +0 -240
- package/references/codex-plugin-cc-analysis.md +0 -706
- package/references/codex-plugin-cc-code-patterns.md +0 -468
- package/references/hosts.json +0 -46
- package/skills/tfx-workspace/async-tests/run-tests.sh +0 -203
- package/skills/tfx-workspace/evals/evals.json +0 -79
- package/skills/tfx-workspace/iteration-1/benchmark.json +0 -524
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/eval_metadata.json +0 -11
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/old_skill/grading.json +0 -25
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/old_skill/outputs/analysis.md +0 -154
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/old_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/with_skill/grading.json +0 -25
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/with_skill/outputs/analysis.md +0 -126
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/with_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/eval_metadata.json +0 -11
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/old_skill/grading.json +0 -25
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/old_skill/outputs/analysis.md +0 -119
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/old_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/with_skill/grading.json +0 -25
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/with_skill/outputs/analysis.md +0 -115
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/with_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/eval_metadata.json +0 -10
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/old_skill/grading.json +0 -20
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/old_skill/outputs/analysis.md +0 -86
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/old_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/with_skill/grading.json +0 -20
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/with_skill/outputs/analysis.md +0 -81
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/with_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/multi-team-creation/eval_metadata.json +0 -12
- package/skills/tfx-workspace/iteration-1/multi-team-creation/old_skill/grading.json +0 -30
- package/skills/tfx-workspace/iteration-1/multi-team-creation/old_skill/outputs/analysis.md +0 -316
- package/skills/tfx-workspace/iteration-1/multi-team-creation/old_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/multi-team-creation/with_skill/grading.json +0 -30
- package/skills/tfx-workspace/iteration-1/multi-team-creation/with_skill/outputs/analysis.md +0 -352
- package/skills/tfx-workspace/iteration-1/multi-team-creation/with_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/review.html +0 -1325
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/eval_metadata.json +0 -12
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/old_skill/grading.json +0 -30
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/old_skill/outputs/analysis.md +0 -97
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/old_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/with_skill/grading.json +0 -30
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/with_skill/outputs/analysis.md +0 -94
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/with_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/eval_metadata.json +0 -12
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/old_skill/grading.json +0 -30
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/old_skill/outputs/analysis.md +0 -209
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/old_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/with_skill/grading.json +0 -30
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/with_skill/outputs/analysis.md +0 -193
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/with_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-2/benchmark.json +0 -144
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/eval_metadata.json +0 -13
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/old_skill/grading.json +0 -35
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/old_skill/outputs/analysis.md +0 -382
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/old_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/with_skill/grading.json +0 -35
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/with_skill/outputs/analysis.md +0 -333
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/with_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-2/review.html +0 -1325
- package/skills/tfx-workspace/skill-snapshot/tfx-auto/SKILL.md +0 -217
- package/skills/tfx-workspace/skill-snapshot/tfx-auto-codex/SKILL.md +0 -77
- package/skills/tfx-workspace/skill-snapshot/tfx-codex/SKILL.md +0 -65
- package/skills/tfx-workspace/skill-snapshot/tfx-doctor/SKILL.md +0 -94
- package/skills/tfx-workspace/skill-snapshot/tfx-gemini/SKILL.md +0 -82
- package/skills/tfx-workspace/skill-snapshot/tfx-hub/SKILL.md +0 -133
- package/skills/tfx-workspace/skill-snapshot/tfx-multi/SKILL.md +0 -426
- package/skills/tfx-workspace/skill-snapshot/tfx-setup/SKILL.md +0 -101
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
|
|
3
|
+
"name": "triflux",
|
|
4
|
+
"description": "CLI-first multi-model orchestrator — Codex/Gemini/Claude routing with DAG execution, auto-triage, and cost optimization",
|
|
5
|
+
"owner": {
|
|
6
|
+
"name": "tellang"
|
|
7
|
+
},
|
|
8
|
+
"plugins": [
|
|
9
|
+
{
|
|
10
|
+
"name": "triflux",
|
|
11
|
+
"description": "Tri-CLI orchestrator for Claude Code. Routes tasks across Claude + Codex + Gemini with consensus intelligence, natural language routing, 42 skills, and cross-model review.",
|
|
12
|
+
"version": "10.13.3",
|
|
13
|
+
"author": {
|
|
14
|
+
"name": "tellang"
|
|
15
|
+
},
|
|
16
|
+
"source": {
|
|
17
|
+
"source": "npm",
|
|
18
|
+
"package": "triflux"
|
|
19
|
+
},
|
|
20
|
+
"category": "productivity",
|
|
21
|
+
"homepage": "https://github.com/tellang/triflux",
|
|
22
|
+
"tags": [
|
|
23
|
+
"multi-model",
|
|
24
|
+
"codex",
|
|
25
|
+
"gemini",
|
|
26
|
+
"cli-routing",
|
|
27
|
+
"orchestration",
|
|
28
|
+
"cost-optimization",
|
|
29
|
+
"dag-execution"
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"version": "10.13.3"
|
|
34
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "triflux",
|
|
3
|
+
"version": "10.13.3",
|
|
4
|
+
"description": "CLI-first multi-model orchestrator for Claude Code — route tasks to Codex, Gemini, and Claude",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "tellang"
|
|
7
|
+
},
|
|
8
|
+
"repository": "https://github.com/tellang/triflux",
|
|
9
|
+
"homepage": "https://github.com/tellang/triflux",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"keywords": [
|
|
12
|
+
"claude-code",
|
|
13
|
+
"plugin",
|
|
14
|
+
"codex",
|
|
15
|
+
"gemini",
|
|
16
|
+
"cli-routing",
|
|
17
|
+
"orchestration",
|
|
18
|
+
"multi-model"
|
|
19
|
+
],
|
|
20
|
+
"skills": "./skills/",
|
|
21
|
+
"hooks": "./hooks/hooks.json"
|
|
22
|
+
}
|
package/bin/triflux.mjs
CHANGED
|
@@ -134,7 +134,8 @@ const CLI_COMMAND_SCHEMAS = Object.freeze({
|
|
|
134
134
|
],
|
|
135
135
|
},
|
|
136
136
|
doctor: {
|
|
137
|
-
usage:
|
|
137
|
+
usage:
|
|
138
|
+
"tfx doctor [--fix] [--reset] [--audit] [--diagnose] [--purge-logs] [--json]",
|
|
138
139
|
description: "설치 상태 진단 및 자동 복구",
|
|
139
140
|
options: [
|
|
140
141
|
{
|
|
@@ -158,6 +159,12 @@ const CLI_COMMAND_SCHEMAS = Object.freeze({
|
|
|
158
159
|
description:
|
|
159
160
|
"진단 번들(zip) 생성: spawn-trace + hook timing + system info",
|
|
160
161
|
},
|
|
162
|
+
{
|
|
163
|
+
name: "--purge-logs",
|
|
164
|
+
type: "boolean",
|
|
165
|
+
description:
|
|
166
|
+
"--fix 와 함께 사용. cli-issues.jsonl 에서 7일 초과 항목 물리 삭제 (#144)",
|
|
167
|
+
},
|
|
161
168
|
{
|
|
162
169
|
name: "--json",
|
|
163
170
|
type: "boolean",
|
|
@@ -1742,7 +1749,12 @@ function ensureValidRegistryState() {
|
|
|
1742
1749
|
}
|
|
1743
1750
|
|
|
1744
1751
|
async function cmdDoctor(options = {}) {
|
|
1745
|
-
const {
|
|
1752
|
+
const {
|
|
1753
|
+
fix = false,
|
|
1754
|
+
reset = false,
|
|
1755
|
+
purgeLogs = false,
|
|
1756
|
+
json = false,
|
|
1757
|
+
} = options;
|
|
1746
1758
|
const report = {
|
|
1747
1759
|
status: "ok",
|
|
1748
1760
|
mode: reset ? "reset" : fix ? "fix" : "check",
|
|
@@ -2357,9 +2369,20 @@ async function cmdDoctor(options = {}) {
|
|
|
2357
2369
|
info(`업데이트 권장:\n${formatPsmuxUpdateGuidance(" ")}`);
|
|
2358
2370
|
}
|
|
2359
2371
|
if (psmuxSupport.missingOptionalCommands?.length > 0) {
|
|
2372
|
+
// #144: 단순히 "detach-first hardening 경로에서만 사용" 만으로는 사용자가
|
|
2373
|
+
// 영향 범위와 해결 방법을 알 수 없다. 각 capability 별 영향과 업그레이드 명령을 명시.
|
|
2360
2374
|
info(
|
|
2361
|
-
`선택 capability 미지원: ${psmuxSupport.missingOptionalCommands.join(", ")}
|
|
2375
|
+
`선택 capability 미지원: ${psmuxSupport.missingOptionalCommands.join(", ")}`,
|
|
2362
2376
|
);
|
|
2377
|
+
if (psmuxSupport.missingOptionalCommands.includes("detach-client")) {
|
|
2378
|
+
info(
|
|
2379
|
+
" detach-client: WT 1.24 ConPTY close-race 회피용. WT 기반 병렬 실행(swarm dashboard, tfx-multi wt 모드) 에서 pane freeze/ConPTY hang 위험 증가.",
|
|
2380
|
+
);
|
|
2381
|
+
info(
|
|
2382
|
+
" 해결: psmux v3.4+ 로 업그레이드. 현재 psmux 업그레이드 명령:",
|
|
2383
|
+
);
|
|
2384
|
+
info(`${formatPsmuxUpdateGuidance(" ")}`);
|
|
2385
|
+
}
|
|
2363
2386
|
}
|
|
2364
2387
|
|
|
2365
2388
|
// 기본 셸 확인: psmux 세션의 기본 셸이 PowerShell인지 cmd.exe인지
|
|
@@ -2771,6 +2794,10 @@ async function cmdDoctor(options = {}) {
|
|
|
2771
2794
|
).version;
|
|
2772
2795
|
let cleaned = 0;
|
|
2773
2796
|
|
|
2797
|
+
// #144: 오래된 로그 노이즈 완화 — 7일 초과 항목은 INFO 레벨로 downgrade.
|
|
2798
|
+
// --fix --purge-logs 플래그가 있으면 해당 오래된 항목은 실제 삭제.
|
|
2799
|
+
const STALE_AGE_MS = 7 * 24 * 3600 * 1000;
|
|
2800
|
+
let purged = 0;
|
|
2774
2801
|
for (const [key, g] of Object.entries(groups)) {
|
|
2775
2802
|
const fixVer = KNOWN_FIXES[key];
|
|
2776
2803
|
if (fixVer && semverGte(currentVer, fixVer)) {
|
|
@@ -2785,30 +2812,62 @@ async function cmdDoctor(options = {}) {
|
|
|
2785
2812
|
: age < 86400000
|
|
2786
2813
|
? `${Math.round(age / 3600000)}시간 전`
|
|
2787
2814
|
: `${Math.round(age / 86400000)}일 전`;
|
|
2788
|
-
const
|
|
2789
|
-
|
|
2815
|
+
const isStale = age >= STALE_AGE_MS;
|
|
2816
|
+
if (isStale && fix && purgeLogs) {
|
|
2817
|
+
purged += g.count;
|
|
2818
|
+
continue;
|
|
2819
|
+
}
|
|
2820
|
+
const sev = isStale
|
|
2821
|
+
? `${CYAN}INFO${RESET}`
|
|
2822
|
+
: g.severity === "error"
|
|
2790
2823
|
? `${RED}ERROR${RESET}`
|
|
2791
2824
|
: `${YELLOW}WARN${RESET}`;
|
|
2792
|
-
|
|
2825
|
+
const staleTag = isStale ? " [STALE]" : "";
|
|
2826
|
+
if (isStale) {
|
|
2827
|
+
info(
|
|
2828
|
+
`[${sev}]${staleTag} ${g.cli}/${g.pattern} x${g.count} (최근: ${ago})`,
|
|
2829
|
+
);
|
|
2830
|
+
} else {
|
|
2831
|
+
warn(`[${sev}] ${g.cli}/${g.pattern} x${g.count} (최근: ${ago})`);
|
|
2832
|
+
}
|
|
2793
2833
|
if (g.snippet) info(` ${g.snippet.substring(0, 120)}`);
|
|
2794
2834
|
if (fixVer)
|
|
2795
2835
|
info(` 해결: triflux >= v${fixVer} (npm update -g triflux)`);
|
|
2796
|
-
|
|
2836
|
+
if (isStale && !purgeLogs) {
|
|
2837
|
+
info(` 7일 초과 — 삭제: tfx doctor --fix --purge-logs`);
|
|
2838
|
+
}
|
|
2839
|
+
if (!isStale) issues++;
|
|
2797
2840
|
}
|
|
2798
2841
|
|
|
2799
|
-
//
|
|
2800
|
-
|
|
2842
|
+
// #144 Codex review P2: 두 filter (stale purge + KNOWN_FIXES) 가 각각 원본 entries 로
|
|
2843
|
+
// write 하면 두 번째 write 가 첫 번째 결과를 되살린다. 단일 통합 필터로 한 번만 저장.
|
|
2844
|
+
if (purged > 0 || cleaned > 0) {
|
|
2845
|
+
const now = Date.now();
|
|
2801
2846
|
const remaining = entries.filter((e) => {
|
|
2847
|
+
// purge-logs 로 물리 삭제 대상: 7일 초과
|
|
2848
|
+
if (purged > 0 && now - e.ts >= STALE_AGE_MS) return false;
|
|
2849
|
+
// KNOWN_FIXES 해결된 이슈 제거
|
|
2802
2850
|
const key = `${e.cli}:${e.pattern}`;
|
|
2803
2851
|
const fixVer = KNOWN_FIXES[key];
|
|
2804
|
-
|
|
2852
|
+
if (fixVer && semverGte(currentVer, fixVer)) return false;
|
|
2853
|
+
return true;
|
|
2805
2854
|
});
|
|
2806
2855
|
writeFileSync(
|
|
2807
2856
|
issuesFile,
|
|
2808
2857
|
remaining.map((e) => JSON.stringify(e)).join("\n") +
|
|
2809
2858
|
(remaining.length ? "\n" : ""),
|
|
2810
2859
|
);
|
|
2811
|
-
|
|
2860
|
+
if (purged > 0) {
|
|
2861
|
+
ok(`${purged}개 stale 로그 항목 삭제 (7일 초과)`);
|
|
2862
|
+
report.actions.push({
|
|
2863
|
+
name: "purge-stale-logs",
|
|
2864
|
+
status: "applied",
|
|
2865
|
+
count: purged,
|
|
2866
|
+
});
|
|
2867
|
+
}
|
|
2868
|
+
if (cleaned > 0) {
|
|
2869
|
+
ok(`${cleaned}개 해결된 이슈 자동 정리됨`);
|
|
2870
|
+
}
|
|
2812
2871
|
}
|
|
2813
2872
|
addDoctorCheck(report, {
|
|
2814
2873
|
name: "cli-issues",
|
|
@@ -3373,6 +3432,56 @@ async function cmdDoctor(options = {}) {
|
|
|
3373
3432
|
if (row.actualUrl) info(`actual ${row.actualUrl}`);
|
|
3374
3433
|
}
|
|
3375
3434
|
|
|
3435
|
+
// #144: --fix 모드에서 tfx-hub URL 불일치를 hub status 기준으로 자동 갱신.
|
|
3436
|
+
// Project MCP (.mcp.json) 와 Codex/Claude/Gemini settings 모두 대상.
|
|
3437
|
+
// Codex review P2: fix 성공 시 issues 집계에서 차감해야 doctor 결과가 ok 로 반영됨.
|
|
3438
|
+
let autoFixedMismatches = 0;
|
|
3439
|
+
if (fix && mismatchRows.some((r) => r.name === "tfx-hub")) {
|
|
3440
|
+
try {
|
|
3441
|
+
const hubUrl = mismatchRows.find(
|
|
3442
|
+
(r) => r.name === "tfx-hub",
|
|
3443
|
+
)?.expectedUrl;
|
|
3444
|
+
if (hubUrl) {
|
|
3445
|
+
const { syncHubMcpSettings, syncProjectMcpJson } = await import(
|
|
3446
|
+
"../scripts/sync-hub-mcp-settings.mjs"
|
|
3447
|
+
);
|
|
3448
|
+
const settingsResult = await syncHubMcpSettings({
|
|
3449
|
+
hubUrl,
|
|
3450
|
+
logger: { log() {}, warn() {}, error() {} },
|
|
3451
|
+
});
|
|
3452
|
+
const projectResult = await syncProjectMcpJson({
|
|
3453
|
+
hubUrl,
|
|
3454
|
+
projectRoot: process.cwd(),
|
|
3455
|
+
logger: { log() {}, warn() {}, error() {} },
|
|
3456
|
+
});
|
|
3457
|
+
const totalUpdated =
|
|
3458
|
+
(settingsResult?.updated?.length || 0) +
|
|
3459
|
+
(projectResult?.updated?.length || 0);
|
|
3460
|
+
if (totalUpdated > 0) {
|
|
3461
|
+
ok(`tfx-hub URL ${totalUpdated}개 파일 자동 갱신 (${hubUrl})`);
|
|
3462
|
+
report.actions.push({
|
|
3463
|
+
name: "sync-hub-url",
|
|
3464
|
+
status: "applied",
|
|
3465
|
+
files: [
|
|
3466
|
+
...(settingsResult?.updated || []),
|
|
3467
|
+
...(projectResult?.updated || []),
|
|
3468
|
+
],
|
|
3469
|
+
});
|
|
3470
|
+
// fix 성공 — mismatchRows 중 tfx-hub 엔트리는 해결된 것으로 집계
|
|
3471
|
+
autoFixedMismatches = mismatchRows.filter(
|
|
3472
|
+
(r) => r.name === "tfx-hub",
|
|
3473
|
+
).length;
|
|
3474
|
+
} else {
|
|
3475
|
+
info("tfx-hub URL 자동 갱신: 대상 파일 없음");
|
|
3476
|
+
}
|
|
3477
|
+
}
|
|
3478
|
+
} catch (e) {
|
|
3479
|
+
warn(
|
|
3480
|
+
`tfx-hub URL 자동 갱신 실패: ${e?.message?.split(/\r?\n/)[0] || e}`,
|
|
3481
|
+
);
|
|
3482
|
+
}
|
|
3483
|
+
}
|
|
3484
|
+
|
|
3376
3485
|
for (const row of missingFileRows) {
|
|
3377
3486
|
info(
|
|
3378
3487
|
`${row.label}: ${row.name} 미배치 (${formatPathForDisplay(row.filePath)})`,
|
|
@@ -3395,7 +3504,7 @@ async function cmdDoctor(options = {}) {
|
|
|
3395
3504
|
}
|
|
3396
3505
|
|
|
3397
3506
|
issues += invalidConfigs.length;
|
|
3398
|
-
issues += mismatchRows.length;
|
|
3507
|
+
issues += Math.max(0, mismatchRows.length - autoFixedMismatches);
|
|
3399
3508
|
issues += stdioRows.length;
|
|
3400
3509
|
}
|
|
3401
3510
|
}
|
|
@@ -5508,7 +5617,8 @@ async function main() {
|
|
|
5508
5617
|
}
|
|
5509
5618
|
const fix = cmdArgs.includes("--fix");
|
|
5510
5619
|
const reset = cmdArgs.includes("--reset");
|
|
5511
|
-
|
|
5620
|
+
const purgeLogs = cmdArgs.includes("--purge-logs");
|
|
5621
|
+
await cmdDoctor({ fix, reset, purgeLogs, json: JSON_OUTPUT });
|
|
5512
5622
|
return;
|
|
5513
5623
|
}
|
|
5514
5624
|
case "mcp":
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "mcp-registry-schema",
|
|
3
|
+
"version": 1,
|
|
4
|
+
"description": "MCP 서버 중앙 레지스트리 — 진실의 원천",
|
|
5
|
+
"defaults": {
|
|
6
|
+
"transport": "hub-url",
|
|
7
|
+
"hub_base": "http://127.0.0.1:27888"
|
|
8
|
+
},
|
|
9
|
+
"servers": {
|
|
10
|
+
"tfx-hub": {
|
|
11
|
+
"transport": "hub-url",
|
|
12
|
+
"url": "http://127.0.0.1:27888/mcp",
|
|
13
|
+
"safe": true,
|
|
14
|
+
"targets": ["claude", "gemini", "codex"],
|
|
15
|
+
"description": "triflux Hub MCP 서버"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"policies": {
|
|
19
|
+
"stdio_action": "replace-with-hub",
|
|
20
|
+
"unknown_server_action": "warn",
|
|
21
|
+
"watched_paths": [
|
|
22
|
+
"~/.gemini/settings.json",
|
|
23
|
+
"~/.codex/config.toml",
|
|
24
|
+
"~/.claude/settings.json",
|
|
25
|
+
"~/.claude/settings.local.json",
|
|
26
|
+
".mcp.json"
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "triflux",
|
|
3
|
-
"version": "10.13.
|
|
3
|
+
"version": "10.13.3",
|
|
4
4
|
"description": "CLI-first multi-model orchestrator for Claude Code — route tasks to Codex, Gemini, and Claude",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -13,26 +13,77 @@
|
|
|
13
13
|
"tfx-doctor-tui": "bin/tfx-doctor-tui.mjs",
|
|
14
14
|
"tfx-setup-tui": "bin/tfx-setup-tui.mjs"
|
|
15
15
|
},
|
|
16
|
-
"engines": {
|
|
17
|
-
"node": ">=18.0.0"
|
|
18
|
-
},
|
|
19
|
-
"dependencies": {
|
|
20
|
-
"@triflux/core": "10.0.1",
|
|
21
|
-
"@triflux/remote": "^10.0.0-alpha.1"
|
|
22
|
-
},
|
|
23
16
|
"files": [
|
|
24
17
|
"bin",
|
|
18
|
+
"tui",
|
|
19
|
+
"hub",
|
|
20
|
+
"config",
|
|
25
21
|
"skills",
|
|
22
|
+
"!skills/tfx-workspace",
|
|
23
|
+
"!**/failure-reports",
|
|
24
|
+
"scripts",
|
|
26
25
|
"hooks",
|
|
27
26
|
"hud",
|
|
28
|
-
"scripts",
|
|
29
|
-
"hub",
|
|
30
27
|
"mesh",
|
|
31
|
-
"
|
|
32
|
-
"CLAUDE.md",
|
|
28
|
+
".claude-plugin",
|
|
33
29
|
"README.md",
|
|
30
|
+
"README.ko.md",
|
|
34
31
|
"LICENSE"
|
|
35
32
|
],
|
|
33
|
+
"workspaces": [
|
|
34
|
+
"packages/core",
|
|
35
|
+
"packages/remote",
|
|
36
|
+
"packages/triflux"
|
|
37
|
+
],
|
|
38
|
+
"scripts": {
|
|
39
|
+
"pack": "node scripts/pack.mjs all",
|
|
40
|
+
"pack:core": "node scripts/pack.mjs core",
|
|
41
|
+
"pack:remote": "node scripts/pack.mjs remote",
|
|
42
|
+
"setup": "node scripts/setup.mjs",
|
|
43
|
+
"preinstall": "node scripts/preinstall.mjs",
|
|
44
|
+
"postinstall": "node scripts/setup.mjs",
|
|
45
|
+
"lint": "biome check bin config hooks hub hud mesh scripts tests .claude-plugin .github package.json package-lock.json biome.json",
|
|
46
|
+
"lint:fix": "biome check --write bin config hooks hub hud mesh scripts tests .claude-plugin .github package.json package-lock.json biome.json",
|
|
47
|
+
"health": "npm test && npm run lint",
|
|
48
|
+
"test": "node scripts/test-lock.mjs --test --test-force-exit --test-concurrency=8 \"tests/**/*.test.mjs\" \"scripts/__tests__/**/*.test.mjs\"",
|
|
49
|
+
"test:unit": "node scripts/test-lock.mjs --test --test-force-exit --test-concurrency=8 tests/unit/**/*.test.mjs",
|
|
50
|
+
"test:integration": "node scripts/test-lock.mjs --test --test-force-exit --test-concurrency=8 tests/integration/**/*.test.mjs",
|
|
51
|
+
"test:route-smoke": "node scripts/test-lock.mjs --test scripts/test-tfx-route-no-claude-native.mjs",
|
|
52
|
+
"test:contract": "node scripts/test-lock.mjs --test --test-force-exit --test-concurrency=8 tests/contract/**/*.test.mjs",
|
|
53
|
+
"test:coverage": "node --experimental-test-coverage --test-coverage-lines=60 --test-coverage-functions=60 --test --test-force-exit --test-concurrency=8 \"tests/**/*.test.mjs\"",
|
|
54
|
+
"gen:skill-docs": "node scripts/gen-skill-docs.mjs",
|
|
55
|
+
"gen:skill-manifest": "node scripts/gen-skill-manifest.mjs",
|
|
56
|
+
"release:check-sync": "node scripts/release/check-sync.mjs",
|
|
57
|
+
"release:check-sync:fix": "node scripts/release/check-sync.mjs --fix",
|
|
58
|
+
"release:check-mirror": "node scripts/release/check-packages-mirror.mjs",
|
|
59
|
+
"release:check-mirror:fix": "node scripts/release/check-packages-mirror.mjs --fix",
|
|
60
|
+
"release:bump": "node scripts/release/bump-version.mjs",
|
|
61
|
+
"release:prepare": "node scripts/release/prepare.mjs",
|
|
62
|
+
"release:publish": "node scripts/release/publish.mjs",
|
|
63
|
+
"release:verify": "node scripts/release/verify.mjs"
|
|
64
|
+
},
|
|
65
|
+
"engines": {
|
|
66
|
+
"node": ">=18.0.0"
|
|
67
|
+
},
|
|
68
|
+
"repository": {
|
|
69
|
+
"type": "git",
|
|
70
|
+
"url": "git+https://github.com/tellang/triflux.git"
|
|
71
|
+
},
|
|
72
|
+
"homepage": "https://github.com/tellang/triflux#readme",
|
|
73
|
+
"author": "tellang",
|
|
74
|
+
"license": "MIT",
|
|
75
|
+
"dependencies": {
|
|
76
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
77
|
+
"better-sqlite3": "^12.6.2",
|
|
78
|
+
"pino": "^10.3.1",
|
|
79
|
+
"pino-pretty": "^13.1.3",
|
|
80
|
+
"systray2": "^2.1.4",
|
|
81
|
+
"zod": "^4.0.0"
|
|
82
|
+
},
|
|
83
|
+
"devDependencies": {
|
|
84
|
+
"@biomejs/biome": "^2.0.0",
|
|
85
|
+
"knip": "^6.3.0"
|
|
86
|
+
},
|
|
36
87
|
"keywords": [
|
|
37
88
|
"claude-code",
|
|
38
89
|
"plugin",
|
|
@@ -43,13 +94,5 @@
|
|
|
43
94
|
"multi-model",
|
|
44
95
|
"triflux",
|
|
45
96
|
"tfx"
|
|
46
|
-
]
|
|
47
|
-
"author": "tellang",
|
|
48
|
-
"license": "MIT",
|
|
49
|
-
"homepage": "https://github.com/tellang/triflux#readme",
|
|
50
|
-
"repository": {
|
|
51
|
-
"type": "git",
|
|
52
|
-
"url": "git+https://github.com/tellang/triflux.git",
|
|
53
|
-
"directory": "packages/triflux"
|
|
54
|
-
}
|
|
97
|
+
]
|
|
55
98
|
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// scripts/__tests__/setup-cleanup-stale-skills.test.mjs
|
|
2
|
+
// #144: cleanupStaleSkills 가 nested directory 를 가진 stale 스킬도 재귀 삭제하는지 확인.
|
|
3
|
+
//
|
|
4
|
+
// 이전 구현은 top-level 파일만 unlinkSync → 하위 폴더 있는 과거 스킬
|
|
5
|
+
// (tfx-deep-*, tfx-codex-swarm 등) 은 제거 실패 → "triflux update 돌려도 13개 그대로" UX bug.
|
|
6
|
+
|
|
7
|
+
import assert from "node:assert/strict";
|
|
8
|
+
import {
|
|
9
|
+
existsSync,
|
|
10
|
+
mkdirSync,
|
|
11
|
+
mkdtempSync,
|
|
12
|
+
rmSync,
|
|
13
|
+
writeFileSync,
|
|
14
|
+
} from "node:fs";
|
|
15
|
+
import { tmpdir } from "node:os";
|
|
16
|
+
import path from "node:path";
|
|
17
|
+
import { after, describe, it } from "node:test";
|
|
18
|
+
|
|
19
|
+
const SETUP_MJS_URL = new URL("../setup.mjs", import.meta.url).href;
|
|
20
|
+
const { cleanupStaleSkills } = await import(SETUP_MJS_URL);
|
|
21
|
+
|
|
22
|
+
describe("#144 cleanupStaleSkills — 재귀 삭제", () => {
|
|
23
|
+
const cleanupDirs = [];
|
|
24
|
+
after(() => {
|
|
25
|
+
for (const d of cleanupDirs) rmSync(d, { recursive: true, force: true });
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
function setupFixture() {
|
|
29
|
+
const root = mkdtempSync(path.join(tmpdir(), "tfx-cleanup-"));
|
|
30
|
+
cleanupDirs.push(root);
|
|
31
|
+
const installedDir = path.join(root, "installed");
|
|
32
|
+
const pkgDir = path.join(root, "pkg");
|
|
33
|
+
mkdirSync(installedDir, { recursive: true });
|
|
34
|
+
mkdirSync(pkgDir, { recursive: true });
|
|
35
|
+
// pkg 에는 tfx-auto 만 있음 (나머지는 installed 에서 stale 로 감지)
|
|
36
|
+
mkdirSync(path.join(pkgDir, "tfx-auto"), { recursive: true });
|
|
37
|
+
return { installedDir, pkgDir };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
it("nested directory 가 있는 stale 스킬도 전부 제거된다 (과거 회귀 bug)", () => {
|
|
41
|
+
const { installedDir, pkgDir } = setupFixture();
|
|
42
|
+
// stale 스킬: top-level 파일 + nested 디렉토리
|
|
43
|
+
const staleSkill = path.join(installedDir, "tfx-deep-review");
|
|
44
|
+
mkdirSync(staleSkill, { recursive: true });
|
|
45
|
+
writeFileSync(path.join(staleSkill, "SKILL.md"), "# deprecated");
|
|
46
|
+
const nested = path.join(staleSkill, "snapshot");
|
|
47
|
+
mkdirSync(nested, { recursive: true });
|
|
48
|
+
writeFileSync(path.join(nested, "data.json"), "{}");
|
|
49
|
+
mkdirSync(path.join(nested, "sub"), { recursive: true });
|
|
50
|
+
writeFileSync(path.join(nested, "sub", "more.txt"), "xxx");
|
|
51
|
+
|
|
52
|
+
// 유지해야 할 스킬 (pkg 에 있음)
|
|
53
|
+
mkdirSync(path.join(installedDir, "tfx-auto"), { recursive: true });
|
|
54
|
+
writeFileSync(path.join(installedDir, "tfx-auto", "SKILL.md"), "# ok");
|
|
55
|
+
|
|
56
|
+
const result = cleanupStaleSkills(installedDir, pkgDir);
|
|
57
|
+
assert.equal(result.count, 1);
|
|
58
|
+
assert.deepEqual(result.removed, ["tfx-deep-review"]);
|
|
59
|
+
assert.equal(existsSync(staleSkill), false, "nested dir 포함 전부 삭제");
|
|
60
|
+
assert.equal(
|
|
61
|
+
existsSync(path.join(installedDir, "tfx-auto")),
|
|
62
|
+
true,
|
|
63
|
+
"pkg 에 있는 스킬은 보존",
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("top-level 파일만 있는 stale 스킬도 제거된다 (legacy behavior 회귀 방지)", () => {
|
|
68
|
+
const { installedDir, pkgDir } = setupFixture();
|
|
69
|
+
const staleSkill = path.join(installedDir, "tfx-autoresearch");
|
|
70
|
+
mkdirSync(staleSkill, { recursive: true });
|
|
71
|
+
writeFileSync(path.join(staleSkill, "SKILL.md"), "# deprecated");
|
|
72
|
+
writeFileSync(path.join(staleSkill, "config.json"), "{}");
|
|
73
|
+
|
|
74
|
+
const result = cleanupStaleSkills(installedDir, pkgDir);
|
|
75
|
+
assert.equal(result.count, 1);
|
|
76
|
+
assert.equal(existsSync(staleSkill), false);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("SKILL_ALIASES 에 있는 alias 는 유지된다", () => {
|
|
80
|
+
const { installedDir, pkgDir } = setupFixture();
|
|
81
|
+
// alias (tfx-autopilot) 는 SKILL_ALIASES 에 있으므로 pkgNames 에 자동 포함
|
|
82
|
+
mkdirSync(path.join(installedDir, "tfx-autopilot"), { recursive: true });
|
|
83
|
+
writeFileSync(
|
|
84
|
+
path.join(installedDir, "tfx-autopilot", "SKILL.md"),
|
|
85
|
+
"# alias",
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const result = cleanupStaleSkills(installedDir, pkgDir);
|
|
89
|
+
assert.equal(result.count, 0);
|
|
90
|
+
assert.equal(existsSync(path.join(installedDir, "tfx-autopilot")), true);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("tfx- 접두사 없는 디렉토리는 건드리지 않음", () => {
|
|
94
|
+
const { installedDir, pkgDir } = setupFixture();
|
|
95
|
+
mkdirSync(path.join(installedDir, "other-skill"), { recursive: true });
|
|
96
|
+
writeFileSync(
|
|
97
|
+
path.join(installedDir, "other-skill", "SKILL.md"),
|
|
98
|
+
"# other",
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const result = cleanupStaleSkills(installedDir, pkgDir);
|
|
102
|
+
assert.equal(result.count, 0);
|
|
103
|
+
assert.equal(existsSync(path.join(installedDir, "other-skill")), true);
|
|
104
|
+
});
|
|
105
|
+
});
|
package/scripts/setup.mjs
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
mkdirSync,
|
|
14
14
|
readdirSync,
|
|
15
15
|
readFileSync,
|
|
16
|
+
rmSync,
|
|
16
17
|
unlinkSync,
|
|
17
18
|
writeFileSync,
|
|
18
19
|
} from "fs";
|
|
@@ -430,17 +431,15 @@ function cleanupStaleSkills(installedDir, pkgDir) {
|
|
|
430
431
|
if (pkgNames.has(name)) continue;
|
|
431
432
|
|
|
432
433
|
const skillPath = join(installedDir, name);
|
|
434
|
+
// #144: 재귀 삭제 필요 — 과거 구현은 파일만 unlink 하여 nested 디렉토리가 있는 스킬을
|
|
435
|
+
// 온전히 제거하지 못했다. `tfx-deep-*`, `tfx-codex-swarm` 같은 과거 잔재 디렉토리는
|
|
436
|
+
// workspace/snapshot 같은 하위 폴더를 가지므로 rmSync recursive 가 필수.
|
|
433
437
|
try {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
// rmdir only works on empty dirs; ignore errors for nested
|
|
437
|
-
try {
|
|
438
|
-
readdirSync(skillPath).length === 0 && unlinkSync(skillPath);
|
|
439
|
-
} catch {}
|
|
438
|
+
rmSync(skillPath, { recursive: true, force: true });
|
|
439
|
+
removed.push(name);
|
|
440
440
|
} catch {
|
|
441
|
-
/* best effort */
|
|
441
|
+
/* best effort — next setup/update cycle 에서 재시도 */
|
|
442
442
|
}
|
|
443
|
-
removed.push(name);
|
|
444
443
|
}
|
|
445
444
|
return { count: removed.length, removed };
|
|
446
445
|
}
|
|
@@ -143,7 +143,7 @@ function resolveDefaultMcpConfig(cwd) {
|
|
|
143
143
|
const legacy = resolve(cwd, ".mcp.json");
|
|
144
144
|
if (existsSync(legacy)) return [legacy];
|
|
145
145
|
process.stderr.write(
|
|
146
|
-
"[tfx-route-worker] warning: no MCP config
|
|
146
|
+
"[tfx-route-worker] warning: no project MCP config in cwd — hub status unaffected\n",
|
|
147
147
|
);
|
|
148
148
|
return [];
|
|
149
149
|
}
|
package/scripts/tfx-route.sh
CHANGED
|
@@ -1348,6 +1348,32 @@ heartbeat_monitor() {
|
|
|
1348
1348
|
echo "[tfx-heartbeat] pid=$pid elapsed=${elapsed}s output=${current_size}B status=draining(${post_exit_checks}/${max_post_exit_checks})" >&2
|
|
1349
1349
|
fi
|
|
1350
1350
|
elif [[ "$stall_count" -ge "$stall_threshold" ]]; then
|
|
1351
|
+
# STALL kill (#144/#66 regression guard): stall=threshold+grace 이상 지속 시 SIGTERM→SIGKILL.
|
|
1352
|
+
# 기본 활성화. TFX_STALL_KILL=0 으로 opt-out. grace=30s (기본) 은 SSE/MCP 정상 handshake 여유.
|
|
1353
|
+
local kill_on_stall="${TFX_STALL_KILL:-1}"
|
|
1354
|
+
local kill_grace="${TFX_STALL_KILL_GRACE:-30}"
|
|
1355
|
+
if [[ "$kill_on_stall" -eq 1 && "$stall_count" -ge $((stall_threshold + kill_grace)) ]]; then
|
|
1356
|
+
echo "[tfx-heartbeat] pid=$pid elapsed=${elapsed}s output=${current_size}B status=STALL_KILL stall=${stall_count}s — SIGTERM" >&2
|
|
1357
|
+
kill -TERM "$pid" 2>/dev/null || true
|
|
1358
|
+
local _grace_waited=0
|
|
1359
|
+
while kill -0 "$pid" 2>/dev/null && [[ "$_grace_waited" -lt 5 ]]; do
|
|
1360
|
+
sleep 1
|
|
1361
|
+
_grace_waited=$((_grace_waited + 1))
|
|
1362
|
+
done
|
|
1363
|
+
if kill -0 "$pid" 2>/dev/null; then
|
|
1364
|
+
# Windows/MSYS: POSIX SIGKILL 이 Win32 자식 트리까지 닿지 않는다.
|
|
1365
|
+
# cleanup_workers 와 동일하게 taskkill /T /F 로 트리 종료.
|
|
1366
|
+
case "$(uname -s)" in
|
|
1367
|
+
MINGW*|MSYS*)
|
|
1368
|
+
echo "[tfx-heartbeat] pid=$pid SIGTERM 무시 — taskkill /T /F" >&2
|
|
1369
|
+
MSYS_NO_PATHCONV=1 cmd.exe //c "taskkill /T /F /PID $pid" 2>/dev/null || true ;;
|
|
1370
|
+
*)
|
|
1371
|
+
echo "[tfx-heartbeat] pid=$pid SIGTERM 무시 — SIGKILL 강제" >&2
|
|
1372
|
+
kill -KILL "$pid" 2>/dev/null || true ;;
|
|
1373
|
+
esac
|
|
1374
|
+
fi
|
|
1375
|
+
break
|
|
1376
|
+
fi
|
|
1351
1377
|
echo "[tfx-heartbeat] pid=$pid elapsed=${elapsed}s output=${current_size}B status=STALL stall=${stall_count}s" >&2
|
|
1352
1378
|
else
|
|
1353
1379
|
echo "[tfx-heartbeat] pid=$pid elapsed=${elapsed}s output=${current_size}B status=quiet stall=${stall_count}s" >&2
|
|
@@ -1453,12 +1479,51 @@ _codex_config_swap() {
|
|
|
1453
1479
|
return 0
|
|
1454
1480
|
fi
|
|
1455
1481
|
|
|
1456
|
-
# 백업 생성 (이미 있으면 다른 워커가 swap 중 —
|
|
1482
|
+
# 백업 생성 (이미 있으면 다른 워커가 swap 중 — 단, owner-dead + 백업 안전 복원 시 이어받기)
|
|
1457
1483
|
if [[ -f "$backup" ]]; then
|
|
1458
|
-
|
|
1459
|
-
|
|
1484
|
+
# Owner PID marker (P1 fix): mtime 만으로 stale 을 판정하면 장시간 정상 실행 워커도 오탐.
|
|
1485
|
+
# $backup.owner 에 생성 워커 PID 기록 → kill -0 로 alive 확인. PID 파일 없거나 죽었으면 stale.
|
|
1486
|
+
# mtime 은 신뢰성 낮아 soft 보조 지표로만 사용 (owner 파일 유실 대비 fallback).
|
|
1487
|
+
local owner_file="${backup}.owner"
|
|
1488
|
+
local owner_alive=false
|
|
1489
|
+
local owner_pid=""
|
|
1490
|
+
if [[ -f "$owner_file" ]]; then
|
|
1491
|
+
owner_pid=$(cat "$owner_file" 2>/dev/null | tr -d '[:space:]')
|
|
1492
|
+
if [[ -n "$owner_pid" ]] && kill -0 "$owner_pid" 2>/dev/null; then
|
|
1493
|
+
owner_alive=true
|
|
1494
|
+
fi
|
|
1495
|
+
fi
|
|
1496
|
+
|
|
1497
|
+
if [[ "$owner_alive" == "true" ]]; then
|
|
1498
|
+
echo "[tfx-route] config.toml swap 스킵: 소유 워커 살아있음 (pid=$owner_pid, $backup)" >&2
|
|
1499
|
+
return 0
|
|
1500
|
+
fi
|
|
1501
|
+
|
|
1502
|
+
# Owner dead or unknown — stale 후보. 다만 backup-loss 방지를 위해 원본 복원 먼저.
|
|
1503
|
+
# P2 fix: `rm -f $backup` 후 현재 config 를 새 backup 으로 cp 하면, 이전 워커가 이미
|
|
1504
|
+
# filter 한 상태에서 crash 했을 때 원본이 영구 소실. 여기서 먼저 restore 를 시도해
|
|
1505
|
+
# backup 이 원본을 담고 있는 한 그것을 살린다.
|
|
1506
|
+
local backup_restore_guard_size
|
|
1507
|
+
backup_restore_guard_size=$(wc -c < "$backup" 2>/dev/null | tr -d ' ') || backup_restore_guard_size=0
|
|
1508
|
+
if [[ "$backup_restore_guard_size" -lt 500 ]]; then
|
|
1509
|
+
# 작은 backup 은 이미 손상된 state. 현재 config 도 필터된 상태일 수 있으므로
|
|
1510
|
+
# 추가 swap 은 상황을 악화시킬 위험. 전체 스킵하고 수동 확인 유도.
|
|
1511
|
+
echo "[tfx-route] stale backup 작음 (size=${backup_restore_guard_size}B, pid=${owner_pid:-?} dead) — swap 스킵, 수동 확인: $backup" >&2
|
|
1512
|
+
return 0
|
|
1513
|
+
fi
|
|
1514
|
+
local stale_tmp="${config}.stale-restore.$$"
|
|
1515
|
+
if cp "$backup" "$stale_tmp" && mv "$stale_tmp" "$config"; then
|
|
1516
|
+
echo "[tfx-route] stale backup 감지 (pid=${owner_pid:-?} dead) — 원본 복원 후 swap 재진행" >&2
|
|
1517
|
+
else
|
|
1518
|
+
echo "[tfx-route] 경고: stale backup 복원 실패, swap 스킵 (수동 확인: $backup)" >&2
|
|
1519
|
+
rm -f "$stale_tmp" 2>/dev/null
|
|
1520
|
+
return 0
|
|
1521
|
+
fi
|
|
1522
|
+
rm -f "$backup" "$owner_file" 2>/dev/null || true
|
|
1460
1523
|
fi
|
|
1461
1524
|
cp "$config" "$backup"
|
|
1525
|
+
# Owner marker: 이 워커가 backup 소유자임을 기록. 다음 워커의 stale detection 기준.
|
|
1526
|
+
echo "$$" > "${backup}.owner" 2>/dev/null || true
|
|
1462
1527
|
|
|
1463
1528
|
# awk로 필터링: 비허용 MCP 서버 섹션 제거, 나머지 그대로 유지.
|
|
1464
1529
|
# keep="" 은 진입 가드에서 return 됐지만 defense-in-depth 유지.
|
|
@@ -1526,6 +1591,7 @@ _codex_config_swap() {
|
|
|
1526
1591
|
if ! rm -f "$backup"; then
|
|
1527
1592
|
echo "[tfx-route] 경고: backup 삭제 실패: $backup (수동 정리 필요)" >&2
|
|
1528
1593
|
fi
|
|
1594
|
+
rm -f "${backup}.owner" 2>/dev/null || true
|
|
1529
1595
|
echo "[tfx-route] config.toml 복원 완료" >&2
|
|
1530
1596
|
fi
|
|
1531
1597
|
}
|