triflux 10.9.1 → 10.9.2
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/bin/triflux.mjs +2 -3
- package/hooks/hook-registry.json +2 -2
- package/hooks/session-start-fast.mjs +21 -10
- package/hub/pipe.mjs +2 -0
- package/hub/server.mjs +19 -0
- package/hub/state.mjs +2 -1
- package/hud/hud-qos-status.mjs +1 -3
- package/hud/renderers.mjs +5 -9
- package/package.json +1 -1
- package/scripts/hub-ensure.mjs +13 -25
- package/scripts/tfx-route.sh +1 -1
- package/skills/tfx-auto/SKILL.md +26 -22
- package/skills/tfx-hub/SKILL.md +273 -85
- package/skills/tfx-hub/skill.json +2 -2
package/bin/triflux.mjs
CHANGED
|
@@ -1047,14 +1047,13 @@ function cmdSetup(options = {}) {
|
|
|
1047
1047
|
}
|
|
1048
1048
|
{
|
|
1049
1049
|
const claudeGuide = ensureGlobalClaudeRoutingSection(CLAUDE_DIR);
|
|
1050
|
-
if (claudeGuide.skipped)
|
|
1050
|
+
if (claudeGuide.skipped && claudeGuide.reason !== "global_sync_disabled")
|
|
1051
1051
|
warn(`CLAUDE.md 라우팅 섹션 확인 실패: ${claudeGuide.reason}`);
|
|
1052
1052
|
else if (
|
|
1053
1053
|
claudeGuide.action === "created" ||
|
|
1054
1054
|
claudeGuide.action === "updated"
|
|
1055
1055
|
)
|
|
1056
1056
|
ok("CLAUDE.md: 전역 triflux 라우팅 요약 갱신");
|
|
1057
|
-
else ok("CLAUDE.md: 전역 triflux 라우팅 요약 유지");
|
|
1058
1057
|
}
|
|
1059
1058
|
|
|
1060
1059
|
// 스킬 동기화 (~/.claude/skills/{name}/SKILL.md)
|
|
@@ -1847,7 +1846,7 @@ async function cmdDoctor(options = {}) {
|
|
|
1847
1846
|
}
|
|
1848
1847
|
{
|
|
1849
1848
|
const claudeGuide = ensureGlobalClaudeRoutingSection(CLAUDE_DIR);
|
|
1850
|
-
if (claudeGuide.skipped)
|
|
1849
|
+
if (claudeGuide.skipped && claudeGuide.reason !== "global_sync_disabled")
|
|
1851
1850
|
warn(`CLAUDE.md 라우팅 섹션 확인 실패: ${claudeGuide.reason}`);
|
|
1852
1851
|
else if (
|
|
1853
1852
|
claudeGuide.action === "created" ||
|
package/hooks/hook-registry.json
CHANGED
|
@@ -167,8 +167,8 @@
|
|
|
167
167
|
"priority": 2,
|
|
168
168
|
"enabled": true,
|
|
169
169
|
"timeout": 8,
|
|
170
|
-
"blocking":
|
|
171
|
-
"description": "tfx-hub 서비스 헬스체크 및 시작"
|
|
170
|
+
"blocking": true,
|
|
171
|
+
"description": "tfx-hub 서비스 헬스체크 및 시작 (BLOCKING — 세션 시작 전 Hub 준비 보장)"
|
|
172
172
|
},
|
|
173
173
|
{
|
|
174
174
|
"id": "tfx-preflight-cache",
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
// 6개 훅을 1개 node 프로세스 안에서 실행하여 콜드스타트 7회 → 1회로 줄인다.
|
|
5
5
|
//
|
|
6
6
|
// 분류:
|
|
7
|
-
// BLOCKING (직렬, stdout 반환 전 완료): setup.runCritical, mcp-safety-guard.run
|
|
8
|
-
// DEFERRED (병렬, 실패해도 안 죽음):
|
|
7
|
+
// BLOCKING (직렬, stdout 반환 전 완료): setup.runCritical, mcp-safety-guard.run, hub-ensure.run
|
|
8
|
+
// DEFERRED (병렬, 실패해도 안 죽음): mcp-gateway-ensure.run, setup.runDeferred
|
|
9
9
|
// BACKGROUND (fire-and-forget): preflight-cache.run
|
|
10
10
|
//
|
|
11
11
|
// external source 훅 (session-vault 등)은 여전히 execFile로 실행된다.
|
|
@@ -55,6 +55,24 @@ async function runBlocking(stdinData) {
|
|
|
55
55
|
log.error({ hook: "mcp-safety-guard", err: String(err.message || err) }, "hook.failed");
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
// 3. hub-ensure — Hub 필수 인프라, BLOCKING으로 실행
|
|
59
|
+
try {
|
|
60
|
+
const t0 = performance.now();
|
|
61
|
+
const hubMod = await importMod(join(SCRIPTS, "hub-ensure.mjs"));
|
|
62
|
+
const result = await hubMod.run(stdinData);
|
|
63
|
+
const dur = performance.now() - t0;
|
|
64
|
+
timings.push({ hook: "hub-ensure", dur_ms: Math.round(dur) });
|
|
65
|
+
if (result?.stdout) output.stdout += result.stdout + "\n";
|
|
66
|
+
if (result?.stderr) output.stderr += result.stderr + "\n";
|
|
67
|
+
if (result?.code !== 0) {
|
|
68
|
+
log.warn({ hook: "hub-ensure", dur_ms: Math.round(dur), code: result?.code }, "hook.warn");
|
|
69
|
+
} else {
|
|
70
|
+
log.info({ hook: "hub-ensure", dur_ms: Math.round(dur) }, "hook.completed");
|
|
71
|
+
}
|
|
72
|
+
} catch (err) {
|
|
73
|
+
log.error({ hook: "hub-ensure", err: String(err.message || err) }, "hook.failed");
|
|
74
|
+
}
|
|
75
|
+
|
|
58
76
|
return { ...output, timings };
|
|
59
77
|
}
|
|
60
78
|
|
|
@@ -65,13 +83,6 @@ async function runBlocking(stdinData) {
|
|
|
65
83
|
*/
|
|
66
84
|
function runDeferred(stdinData) {
|
|
67
85
|
const tasks = [
|
|
68
|
-
{
|
|
69
|
-
name: "hub-ensure",
|
|
70
|
-
fn: async () => {
|
|
71
|
-
const mod = await importMod(join(SCRIPTS, "hub-ensure.mjs"));
|
|
72
|
-
return mod.run(stdinData);
|
|
73
|
-
},
|
|
74
|
-
},
|
|
75
86
|
{
|
|
76
87
|
name: "mcp-gateway-ensure",
|
|
77
88
|
fn: async () => {
|
|
@@ -134,7 +145,7 @@ export async function execute(stdinData, externalHooks = []) {
|
|
|
134
145
|
runBackground(stdinData);
|
|
135
146
|
|
|
136
147
|
const totalDur = performance.now() - totalStart;
|
|
137
|
-
log.info({ total_ms: Math.round(totalDur), blocking_count:
|
|
148
|
+
log.info({ total_ms: Math.round(totalDur), blocking_count: 3, deferred_count: 2, bg_count: 1 }, "session-start.done");
|
|
138
149
|
|
|
139
150
|
return {
|
|
140
151
|
stdout: blocking.stdout,
|
package/hub/pipe.mjs
CHANGED
|
@@ -125,6 +125,7 @@ export function createPipeServer({
|
|
|
125
125
|
heartbeatTtlMs = DEFAULT_HEARTBEAT_TTL_MS,
|
|
126
126
|
delegatorService = null,
|
|
127
127
|
hitlManager = null,
|
|
128
|
+
onActivity = null,
|
|
128
129
|
} = {}) {
|
|
129
130
|
if (!router) {
|
|
130
131
|
throw new Error("router is required");
|
|
@@ -164,6 +165,7 @@ export function createPipeServer({
|
|
|
164
165
|
|
|
165
166
|
function touchClient(client) {
|
|
166
167
|
client.lastHeartbeatMs = Date.now();
|
|
168
|
+
if (onActivity) onActivity();
|
|
167
169
|
}
|
|
168
170
|
|
|
169
171
|
function resolveAgentId(client, payload) {
|
package/hub/server.mjs
CHANGED
|
@@ -580,6 +580,7 @@ export async function startHub({
|
|
|
580
580
|
sessionId,
|
|
581
581
|
delegatorService,
|
|
582
582
|
hitlManager: hitl,
|
|
583
|
+
onActivity: markRequestActivity,
|
|
583
584
|
});
|
|
584
585
|
const assignCallbacks = createAssignCallbackServer({ store, sessionId });
|
|
585
586
|
const tools = createTools(store, router, hitl, pipe);
|
|
@@ -1660,6 +1661,23 @@ if (selfRun) {
|
|
|
1660
1661
|
const port = parseInt(process.env.TFX_HUB_PORT || "27888", 10);
|
|
1661
1662
|
const dbPath = process.env.TFX_HUB_DB || undefined;
|
|
1662
1663
|
|
|
1664
|
+
const cleanupPidFile = () => {
|
|
1665
|
+
try {
|
|
1666
|
+
unlinkSync(PID_FILE);
|
|
1667
|
+
} catch {}
|
|
1668
|
+
};
|
|
1669
|
+
|
|
1670
|
+
process.on("unhandledRejection", (err) => {
|
|
1671
|
+
hubLog.fatal({ err }, "hub.unhandledRejection");
|
|
1672
|
+
cleanupPidFile();
|
|
1673
|
+
process.exit(1);
|
|
1674
|
+
});
|
|
1675
|
+
process.on("uncaughtException", (err) => {
|
|
1676
|
+
hubLog.fatal({ err }, "hub.uncaughtException");
|
|
1677
|
+
cleanupPidFile();
|
|
1678
|
+
process.exit(1);
|
|
1679
|
+
});
|
|
1680
|
+
|
|
1663
1681
|
startHub({ port, dbPath })
|
|
1664
1682
|
.then((info) => {
|
|
1665
1683
|
const shutdown = async (signal) => {
|
|
@@ -1678,6 +1696,7 @@ if (selfRun) {
|
|
|
1678
1696
|
})
|
|
1679
1697
|
.catch((error) => {
|
|
1680
1698
|
hubLog.fatal({ err: error }, "hub.start_failed");
|
|
1699
|
+
cleanupPidFile();
|
|
1681
1700
|
process.exit(1);
|
|
1682
1701
|
});
|
|
1683
1702
|
}
|
package/hub/state.mjs
CHANGED
|
@@ -256,8 +256,9 @@ export async function acquireLock(options = {}) {
|
|
|
256
256
|
const raw = readFileSync(lockPath, "utf8");
|
|
257
257
|
const data = parseJson(raw, {});
|
|
258
258
|
const stats = statSync(lockPath);
|
|
259
|
+
const STALE_LOCK_AGE_MS = 60_000;
|
|
259
260
|
const staleByPid = !isPidAlive(data?.pid);
|
|
260
|
-
const staleByAge = Date.now() - stats.mtimeMs >
|
|
261
|
+
const staleByAge = Date.now() - stats.mtimeMs > STALE_LOCK_AGE_MS;
|
|
261
262
|
if (staleByPid || staleByAge) {
|
|
262
263
|
try {
|
|
263
264
|
unlinkSync(lockPath);
|
package/hud/hud-qos-status.mjs
CHANGED
|
@@ -138,9 +138,7 @@ async function main() {
|
|
|
138
138
|
// 실측 데이터 추출
|
|
139
139
|
const stdin = await stdinPromise;
|
|
140
140
|
const contextView = buildContextUsageView(stdin, contextSnapshot);
|
|
141
|
-
const claudeUsage = claudeUsageSnapshot.
|
|
142
|
-
? { ...(claudeUsageSnapshot.data || {}), stale: true }
|
|
143
|
-
: claudeUsageSnapshot.data;
|
|
141
|
+
const claudeUsage = claudeUsageSnapshot.data;
|
|
144
142
|
const codexEmail = getCodexEmail();
|
|
145
143
|
const geminiEmail = getGeminiEmail();
|
|
146
144
|
const codexBuckets = codexSnapshot.buckets;
|
package/hud/renderers.mjs
CHANGED
|
@@ -263,8 +263,6 @@ export function getMicroLine(
|
|
|
263
263
|
combinedSvPct,
|
|
264
264
|
) {
|
|
265
265
|
const ctxView = contextView || buildContextUsageView({}, null);
|
|
266
|
-
const staleMarker = claudeUsage?.stale ? ` ${dim("[stale]")}` : "";
|
|
267
|
-
|
|
268
266
|
// Claude 5h/1w
|
|
269
267
|
const cF =
|
|
270
268
|
claudeUsage?.fiveHourPercent != null
|
|
@@ -319,7 +317,7 @@ export function getMicroLine(
|
|
|
319
317
|
`${bold(codexWhite("x"))}${dim(":")}${xVal} ` +
|
|
320
318
|
`${bold(geminiBlue("g"))}${dim(":")}${gVal} ` +
|
|
321
319
|
`${dim("sv:")}${sv} ` +
|
|
322
|
-
`${dim("CTX:")}${colorByPercent(ctxView.percent, ctxView.display)}
|
|
320
|
+
`${dim("CTX:")}${colorByPercent(ctxView.percent, ctxView.display)}`;
|
|
323
321
|
return truncateAnsi(line, cols);
|
|
324
322
|
}
|
|
325
323
|
|
|
@@ -334,8 +332,6 @@ export function getClaudeRows(
|
|
|
334
332
|
) {
|
|
335
333
|
const ctxView = contextView || buildContextUsageView({}, null);
|
|
336
334
|
const prefix = `${bold(claudeOrange("c"))}:`;
|
|
337
|
-
const staleMarker = claudeUsage?.stale ? ` ${dim("[stale]")}` : "";
|
|
338
|
-
|
|
339
335
|
// 절약 퍼센트
|
|
340
336
|
const svStr = formatSvPct(combinedSvPct || 0);
|
|
341
337
|
const svSuffix = `${dim("sv:")}${svStr}`;
|
|
@@ -388,18 +384,18 @@ export function getClaudeRows(
|
|
|
388
384
|
hasData && weeklyPercent != null
|
|
389
385
|
? colorByProvider(weeklyPercent, `${weeklyPercent}%`, claudeOrange)
|
|
390
386
|
: dim("--");
|
|
391
|
-
const quotaSection = `${fShort}${dim("/")}${wShort}
|
|
387
|
+
const quotaSection = `${fShort}${dim("/")}${wShort}`;
|
|
392
388
|
return [{ prefix, left: quotaSection, right: "" }];
|
|
393
389
|
}
|
|
394
390
|
|
|
395
391
|
if (currentTier === "minimal") {
|
|
396
|
-
const quotaSection = `${dim("5h:")}${fStr} ${dim("1w:")}${wStr}
|
|
392
|
+
const quotaSection = `${dim("5h:")}${fStr} ${dim("1w:")}${wStr}`;
|
|
397
393
|
const right = `${dim("CTX:")}${colorByPercent(ctxView.percent, ctxView.display)}`;
|
|
398
394
|
return [{ prefix, left: quotaSection, right }];
|
|
399
395
|
}
|
|
400
396
|
|
|
401
397
|
if (currentTier === "compact") {
|
|
402
|
-
const quotaSection = `${dim("5h:")}${fStr} ${dim(fTime)} ${dim("1w:")}${wStr} ${dim(wTime)}
|
|
398
|
+
const quotaSection = `${dim("5h:")}${fStr} ${dim(fTime)} ${dim("1w:")}${wStr} ${dim(wTime)}`;
|
|
403
399
|
const warning = ctxView.warningTag
|
|
404
400
|
? ` ${dim("|")} ${yellow(ctxView.warningTag)}`
|
|
405
401
|
: "";
|
|
@@ -408,7 +404,7 @@ export function getClaudeRows(
|
|
|
408
404
|
}
|
|
409
405
|
|
|
410
406
|
// full tier (>= 120 cols)
|
|
411
|
-
const quotaSection = `${dim("5h:")}${fBar}${fStr} ${dim(fTime)} ${dim("1w:")}${wBar}${wStr} ${dim(wTime)}
|
|
407
|
+
const quotaSection = `${dim("5h:")}${fBar}${fStr} ${dim(fTime)} ${dim("1w:")}${wBar}${wStr} ${dim(wTime)}`;
|
|
412
408
|
const warning = ctxView.warningTag
|
|
413
409
|
? ` ${dim("|")} ${yellow(ctxView.warningTag)}`
|
|
414
410
|
: "";
|
package/package.json
CHANGED
package/scripts/hub-ensure.mjs
CHANGED
|
@@ -52,12 +52,12 @@ function resolveHubTarget() {
|
|
|
52
52
|
|
|
53
53
|
async function isHubHealthy(host, port) {
|
|
54
54
|
try {
|
|
55
|
-
const res = await fetch(`${buildHubBaseUrl(host, port)}/
|
|
55
|
+
const res = await fetch(`${buildHubBaseUrl(host, port)}/health`, {
|
|
56
56
|
signal: AbortSignal.timeout(1000),
|
|
57
57
|
});
|
|
58
58
|
if (!res.ok) return false;
|
|
59
59
|
const data = await res.json();
|
|
60
|
-
return data?.
|
|
60
|
+
return data?.ok === true;
|
|
61
61
|
} catch {
|
|
62
62
|
return false;
|
|
63
63
|
}
|
|
@@ -69,25 +69,13 @@ function startHubDetached(port) {
|
|
|
69
69
|
|
|
70
70
|
try {
|
|
71
71
|
const env = { ...process.env, TFX_HUB_PORT: String(port) };
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
windowsHide: true,
|
|
80
|
-
},
|
|
81
|
-
);
|
|
82
|
-
child.unref();
|
|
83
|
-
} else {
|
|
84
|
-
const child = spawn(process.execPath, [serverPath], {
|
|
85
|
-
env,
|
|
86
|
-
detached: true,
|
|
87
|
-
stdio: "ignore",
|
|
88
|
-
});
|
|
89
|
-
child.unref();
|
|
90
|
-
}
|
|
72
|
+
const child = spawn(process.execPath, [serverPath], {
|
|
73
|
+
env,
|
|
74
|
+
detached: true,
|
|
75
|
+
stdio: "ignore",
|
|
76
|
+
windowsHide: true,
|
|
77
|
+
});
|
|
78
|
+
child.unref();
|
|
91
79
|
return true;
|
|
92
80
|
} catch {
|
|
93
81
|
return false;
|
|
@@ -114,13 +102,13 @@ export async function run(stdinData) {
|
|
|
114
102
|
|
|
115
103
|
const started = startHubDetached(port);
|
|
116
104
|
if (!started) {
|
|
117
|
-
return { code:
|
|
105
|
+
return { code: 1, stdout: "", stderr: "[hub-ensure] hub 시작 실패" };
|
|
118
106
|
}
|
|
119
107
|
|
|
120
|
-
const ready = await waitForHubReady(host, port,
|
|
108
|
+
const ready = await waitForHubReady(host, port, 5000);
|
|
121
109
|
return {
|
|
122
|
-
code: 0,
|
|
123
|
-
stdout: ready ? "hub: ok" : "hub: starting",
|
|
110
|
+
code: ready ? 0 : 2,
|
|
111
|
+
stdout: ready ? "hub: ok" : "hub: starting (timeout)",
|
|
124
112
|
stderr: "",
|
|
125
113
|
};
|
|
126
114
|
}
|
package/scripts/tfx-route.sh
CHANGED
|
@@ -920,7 +920,7 @@ route_agent() {
|
|
|
920
920
|
TFX_CLI_MODE="${TFX_CLI_MODE:-auto}"
|
|
921
921
|
TFX_NO_CLAUDE_NATIVE="${TFX_NO_CLAUDE_NATIVE:-0}"
|
|
922
922
|
TFX_VERIFIER_OVERRIDE="${TFX_VERIFIER_OVERRIDE:-auto}"
|
|
923
|
-
TFX_CODEX_TRANSPORT="${TFX_CODEX_TRANSPORT:-
|
|
923
|
+
TFX_CODEX_TRANSPORT="${TFX_CODEX_TRANSPORT:-auto}"
|
|
924
924
|
# Preflight 캐시 일괄 로드 — CLI/Hub 가용성 + Codex 요금제를 환경변수로 내보냄
|
|
925
925
|
# 하위 프로세스(스킬 포함)가 TFX_CODEX_OK, TFX_GEMINI_OK, TFX_HUB_OK로 즉시 참조 가능
|
|
926
926
|
if [[ -z "${TFX_PREFLIGHT_LOADED:-}" ]]; then
|
package/skills/tfx-auto/SKILL.md
CHANGED
|
@@ -93,21 +93,22 @@ dispatch 시 해당 스킬을 Skill 도구로 호출하고 **이 워크플로우
|
|
|
93
93
|
> 2. **비용**: Codex 우선 → Gemini → Claude 최후 수단. `claude` 선택 전 "Codex로 가능한가?" 재확인.
|
|
94
94
|
> 3. **DAG**: SEQUENTIAL/DAG이면 레벨 기반 순차 실행. `.omc/context/{sid}/` 생성, context_output 저장, 실패 시 후속 SKIP.
|
|
95
95
|
> 4. **트리아지**: Codex `exec --full-auto` 분류 + Opus 인라인 분해. Agent 스폰 금지.
|
|
96
|
-
> 5. **thorough
|
|
96
|
+
> 5. **thorough 기본**: `--thorough`가 기본. Opus가 규모(S/M)·커맨드 숏컷 판단 시 자동 경량화 가능. `--quick`은 명시적 옵트아웃.
|
|
97
97
|
> 6. **직접 수정 금지**: implement/review/analyze 등 커맨드 숏컷 실행 시 절대로 Edit/Write 도구로 직접 코드를 수정하지 마라. 반드시 Bash(tfx-route.sh)를 통해 Codex/Gemini에 위임하라. 작업이 아무리 사소해도 예외 없음.
|
|
98
98
|
|
|
99
99
|
## 모드
|
|
100
100
|
|
|
101
101
|
| 입력 형식 | 모드 | 트리아지 |
|
|
102
102
|
|-----------|------|----------|
|
|
103
|
-
| `/implement JWT 추가` | 커맨드 숏컷 (
|
|
104
|
-
| `/tfx-auto "리팩터링 + UI"` | 자동 (
|
|
105
|
-
| `/tfx-auto -
|
|
106
|
-
| `/tfx-auto --
|
|
107
|
-
| `/tfx-auto 3:codex "리뷰"` | 수동 (
|
|
103
|
+
| `/implement JWT 추가` | 커맨드 숏컷 (thorough) | Opus 판단 → 규모 S면 자동 경량화 |
|
|
104
|
+
| `/tfx-auto "리팩터링 + UI"` | 자동 (thorough) | Codex 분류 → Opus 분해 → Pipeline |
|
|
105
|
+
| `/tfx-auto -q "빠르게 수정"` | 자동 (quick) | Opus 분해만, plan/verify 생략 |
|
|
106
|
+
| `/tfx-auto --quick "빠르게"` | 자동 (quick) | `-q` 동일 |
|
|
107
|
+
| `/tfx-auto 3:codex "리뷰"` | 수동 (thorough) | Opus 분해 + Pipeline |
|
|
108
108
|
|
|
109
|
-
> **tfx-auto는 `--
|
|
110
|
-
>
|
|
109
|
+
> **tfx-auto는 `--thorough`가 기본.** 모든 작업에 plan/verify 파이프라인을 적용한다.
|
|
110
|
+
> Opus가 규모(S)·단순 커맨드 숏컷으로 판단하면 자동 경량화(plan/verify 생략)한다.
|
|
111
|
+
> 명시적 `--quick`/`-q`로 강제 경량화 가능.
|
|
111
112
|
|
|
112
113
|
## 커맨드 숏컷
|
|
113
114
|
|
|
@@ -168,16 +169,16 @@ dispatch 시 해당 스킬을 Skill 도구로 호출하고 **이 워크플로우
|
|
|
168
169
|
|
|
169
170
|
**수동 모드 (`N:agent_type`):** Codex 분류 건너뜀 → Opus가 N개 서브태스크 분해. N > 10 거부.
|
|
170
171
|
|
|
171
|
-
##
|
|
172
|
+
## 파이프라인 (기본: thorough)
|
|
172
173
|
|
|
173
|
-
|
|
174
|
+
`--thorough`가 기본. `--quick`/`-q` 명시 시 경량화. Opus가 규모 S·단순 숏컷으로 판단 시 자동 경량화.
|
|
174
175
|
|
|
175
176
|
```
|
|
176
177
|
분기점은 "실행 전략"이지 "계획"이 아님:
|
|
177
178
|
|
|
178
179
|
TRIAGE
|
|
179
180
|
│
|
|
180
|
-
├─ [thorough] → PIPELINE INIT(plan) → PLAN → PRD → [APPROVAL]
|
|
181
|
+
├─ [기본/thorough] → PIPELINE INIT(plan) → PLAN → PRD → [APPROVAL]
|
|
181
182
|
│ │
|
|
182
183
|
│ ┌───────────────┤
|
|
183
184
|
│ │ │
|
|
@@ -189,8 +190,10 @@ TRIAGE
|
|
|
189
190
|
│ │
|
|
190
191
|
│ VERIFY → FIX loop → COMPLETE
|
|
191
192
|
│
|
|
192
|
-
|
|
193
|
-
|
|
193
|
+
├─ [Opus 자동 경량화] → 규모 S + 단일 파일 → fire-and-forget (plan/verify 생략)
|
|
194
|
+
│
|
|
195
|
+
└─ [--quick 명시] → [1 task] → fire-and-forget
|
|
196
|
+
[2+ tasks] → TEAM EXEC → COLLECT → CLEANUP
|
|
194
197
|
```
|
|
195
198
|
|
|
196
199
|
### 단일 태스크 thorough
|
|
@@ -216,10 +219,11 @@ Plan/PRD/Approval은 tfx-auto에서 실행, 그 후 tfx-multi Phase 3로 전환.
|
|
|
216
219
|
|
|
217
220
|
| 조건 | 실행 경로 | 엔진 |
|
|
218
221
|
|------|----------|------|
|
|
219
|
-
| 1개
|
|
220
|
-
| 1개 +
|
|
221
|
-
|
|
|
222
|
-
| 2개+
|
|
222
|
+
| 1개 (기본 thorough) | tfx-auto 직접 실행 + verify/fix loop | tfx-route.sh |
|
|
223
|
+
| 1개 + Opus 자동 경량화 | tfx-auto 직접 실행 (fire-and-forget) | tfx-route.sh |
|
|
224
|
+
| 1개 + `--quick` 명시 | tfx-auto 직접 실행 (fire-and-forget) | tfx-route.sh |
|
|
225
|
+
| 2개+ (기본 thorough) | Plan/PRD/Approval 후 → headless + verify/fix | headless.mjs |
|
|
226
|
+
| 2개+ + `--quick` 명시 | **headless 직접 실행** (WT 자동 팝업) | headless.mjs |
|
|
223
227
|
| psmux 미설치 fallback | Native Teams (Agent slim wrapper) | native.mjs |
|
|
224
228
|
|
|
225
229
|
> **MANDATORY: 2개+ 서브태스크 시 headless 엔진 필수**
|
|
@@ -229,19 +233,19 @@ Plan/PRD/Approval은 tfx-auto에서 실행, 그 후 tfx-multi Phase 3로 전환.
|
|
|
229
233
|
**전환 방법:**
|
|
230
234
|
|
|
231
235
|
```
|
|
232
|
-
|
|
236
|
+
quick = args에 -q 또는 --quick 명시, 또는 Opus 자동 경량화 판단
|
|
233
237
|
|
|
234
238
|
if subtasks.length >= 2:
|
|
235
239
|
if psmux 설치됨:
|
|
236
240
|
→ Bash("tfx multi --teammate-mode headless --auto-attach --dashboard --assign 'cli:prompt:role' ...")
|
|
237
|
-
→ if
|
|
241
|
+
→ if !quick: verify → fix loop
|
|
238
242
|
else:
|
|
239
243
|
→ fallback: tfx-multi Phase 3 Native Teams (Agent slim wrapper)
|
|
240
244
|
else:
|
|
241
|
-
if
|
|
242
|
-
→
|
|
245
|
+
if quick:
|
|
246
|
+
→ tfx-auto 직접 실행 (fire-and-forget)
|
|
243
247
|
else:
|
|
244
|
-
→
|
|
248
|
+
→ Pipeline init → Plan → PRD → Approval → 직접 실행 → Verify → Fix loop
|
|
245
249
|
```
|
|
246
250
|
|
|
247
251
|
## 실행
|
package/skills/tfx-hub/SKILL.md
CHANGED
|
@@ -1,70 +1,124 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: tfx-hub
|
|
3
3
|
description: >
|
|
4
|
-
tfx-hub MCP 메시지 버스 관리.
|
|
5
|
-
|
|
4
|
+
tfx-hub MCP 메시지 버스 관리. AskUserQuestion 기반 인터랙티브 UI로
|
|
5
|
+
허브 시작/중지/상태확인, MCP 서버 관리, 에이전트 조회, 파이프라인 조회를 수행합니다.
|
|
6
6
|
Use when: hub, 허브, 메시지 버스, message bus, 브릿지, bridge, MCP 서버 관리, 에이전트 통신
|
|
7
7
|
triggers:
|
|
8
8
|
- tfx-hub
|
|
9
|
-
argument-hint: "<start|stop|status|자유형 작업 설명>"
|
|
9
|
+
argument-hint: "<start|stop|status|mcp|자유형 작업 설명>"
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
-
# tfx-hub — MCP 메시지 버스 관리
|
|
12
|
+
# tfx-hub — MCP 메시지 버스 관리
|
|
13
13
|
|
|
14
|
-
> **ARGUMENTS 처리**:
|
|
15
|
-
>
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
> **인프라**: 다른 스킬이 내부적으로 사용. 직접 호출할 필요 없음.
|
|
19
|
-
> CLI 에이전트(Codex/Gemini/Claude) 간 실시간 메시지 허브를 관리합니다.
|
|
20
|
-
> **커맨드 매칭 + fallthrough**: start/stop/status에 매칭되면 즉시 실행,
|
|
21
|
-
> 매칭 안 되면 **hub 도메인 컨텍스트를 활용한 범용 작업**으로 처리합니다.
|
|
14
|
+
> **ARGUMENTS 처리**: `ARGUMENTS: <값>`과 함께 호출되면 해당 값을 입력으로 사용한다.
|
|
15
|
+
> start/stop/status/mcp에 매칭되면 즉시 실행, 나머지는 메인 메뉴를 표시한다.
|
|
22
16
|
|
|
23
17
|
## 입력 해석 규칙
|
|
24
18
|
|
|
25
19
|
```
|
|
26
|
-
/tfx-hub start
|
|
27
|
-
/tfx-hub stop
|
|
28
|
-
/tfx-hub status
|
|
29
|
-
/tfx-hub
|
|
30
|
-
/tfx-hub
|
|
31
|
-
/tfx-hub
|
|
20
|
+
/tfx-hub start → 즉시 실행: 허브 시작
|
|
21
|
+
/tfx-hub stop → 즉시 실행: 허브 중지
|
|
22
|
+
/tfx-hub status → 즉시 실행: 상태 확인
|
|
23
|
+
/tfx-hub mcp → 즉시 실행: MCP 서버 목록
|
|
24
|
+
/tfx-hub → 메인 메뉴 표시
|
|
25
|
+
/tfx-hub 뭔가 → fallthrough: hub 도메인 범용 작업
|
|
32
26
|
```
|
|
33
27
|
|
|
34
|
-
|
|
35
|
-
|
|
28
|
+
## 워크플로우
|
|
29
|
+
|
|
30
|
+
### Step 0: 허브 상태 사전 확인
|
|
31
|
+
|
|
32
|
+
메뉴 표시 전 허브 실행 상태를 먼저 확인한다:
|
|
36
33
|
|
|
37
|
-
fallthrough 라우팅:
|
|
38
34
|
```bash
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
Bash("curl -sf http://127.0.0.1:27888/status 2>/dev/null | node -e \"const d=JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')); console.log(JSON.stringify({running:true, uptime_ms:d.hub.uptime_ms, sessions:d.sessions, queues:d.queues, assigns:d.assigns}))\" 2>/dev/null || echo '{\"running\":false}'")
|
|
36
|
+
```
|
|
41
37
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
# ↑ --profile은 exec 앞에, --skip-git-repo-check은 exec 뒤에
|
|
38
|
+
결과를 `hubState` 변수로 저장한다.
|
|
39
|
+
|
|
40
|
+
### Step 1: 메인 메뉴 (AskUserQuestion)
|
|
46
41
|
|
|
47
|
-
# Claude 네이티브 (탐색/검증)
|
|
48
|
-
Agent(subagent_type="oh-my-claudecode:explore", prompt="{작업}")
|
|
49
42
|
```
|
|
43
|
+
question: "tfx-hub 관리 — 어떤 작업을 수행하시겠습니까?"
|
|
44
|
+
header: "tfx-hub {hubState.running ? '● 실행 중' : '○ 중지됨'} | 세션: {sessions} | 큐: urgent {queues.urgent_depth} / normal {queues.normal_depth} / DLQ {queues.dlq_depth}"
|
|
45
|
+
options:
|
|
46
|
+
- label: "허브 상태 보기"
|
|
47
|
+
description: "상세 상태 — 에이전트, 큐, 파이프라인, assign 현황"
|
|
48
|
+
- label: "허브 시작"
|
|
49
|
+
description: "MCP 서버를 :27888에서 시작"
|
|
50
|
+
- label: "허브 중지"
|
|
51
|
+
description: "실행 중인 허브 프로세스 종료"
|
|
52
|
+
- label: "MCP 서버 관리"
|
|
53
|
+
description: "등록된 MCP 서버 목록 조회, 추가, 제거"
|
|
54
|
+
- label: "파이프라인 조회"
|
|
55
|
+
description: "활성 파이프라인 목록 + 상태"
|
|
56
|
+
- label: "Assign 작업 조회"
|
|
57
|
+
description: "비동기 작업 목록 + 상태"
|
|
58
|
+
- label: "DLQ 관리"
|
|
59
|
+
description: "Dead Letter Queue 조회 + 재시도/삭제"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
허브가 중지됨 상태라면 "허브 시작" 외 항목 선택 시 "허브가 실행 중이 아닙니다. 먼저 시작하시겠습니까?" 확인을 표시한다.
|
|
50
63
|
|
|
51
|
-
|
|
64
|
+
### Step 2: 선택에 따른 분기
|
|
52
65
|
|
|
53
|
-
|
|
66
|
+
#### "허브 상태 보기"
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
Bash("curl -s http://127.0.0.1:27888/status 2>/dev/null || echo '{\"error\":\"hub 미실행\"}'")
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
결과를 파싱하여 표시:
|
|
73
|
+
|
|
74
|
+
```markdown
|
|
75
|
+
## Hub Status
|
|
76
|
+
|
|
77
|
+
| 항목 | 값 |
|
|
78
|
+
|------|-----|
|
|
79
|
+
| 상태 | ● healthy |
|
|
80
|
+
| Uptime | 22m 3s |
|
|
81
|
+
| PID | 24504 |
|
|
82
|
+
| 포트 | 27888 |
|
|
83
|
+
| 인증 | localhost-only |
|
|
84
|
+
| 세션 | 0 |
|
|
85
|
+
|
|
86
|
+
### 큐
|
|
87
|
+
| urgent | normal | DLQ |
|
|
88
|
+
|--------|--------|-----|
|
|
89
|
+
| 0 | 0 | 10 |
|
|
90
|
+
|
|
91
|
+
### Assign
|
|
92
|
+
| queued | running | failed | timed_out |
|
|
93
|
+
|--------|---------|--------|-----------|
|
|
94
|
+
| 0 | 0 | 0 | 1 |
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
표시 후 메인 메뉴로 돌아갈지 AskUserQuestion:
|
|
98
|
+
```
|
|
99
|
+
question: "추가 작업이 있으십니까?"
|
|
100
|
+
options:
|
|
101
|
+
- label: "메인 메뉴로"
|
|
102
|
+
- label: "종료"
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
#### "허브 시작"
|
|
54
106
|
|
|
55
107
|
```bash
|
|
56
108
|
Bash("node hub/server.mjs", run_in_background=true)
|
|
57
109
|
```
|
|
58
110
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
111
|
+
시작 후 2초 대기하여 상태 확인:
|
|
112
|
+
```bash
|
|
113
|
+
Bash("sleep 2 && curl -sf http://127.0.0.1:27888/status >/dev/null 2>&1 && echo 'OK' || echo 'FAIL'")
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
- OK → "허브가 시작되었습니다. http://127.0.0.1:27888"
|
|
117
|
+
- FAIL → "허브 시작에 실패했습니다. `node hub/server.mjs`를 직접 실행해 보세요."
|
|
63
118
|
|
|
64
|
-
|
|
119
|
+
#### "허브 중지"
|
|
65
120
|
|
|
66
121
|
```bash
|
|
67
|
-
# PID 파일에서 프로세스 ID 읽어서 종료
|
|
68
122
|
Bash("node -e \"
|
|
69
123
|
const fs = require('fs');
|
|
70
124
|
const path = require('path');
|
|
@@ -79,31 +133,179 @@ Bash("node -e \"
|
|
|
79
133
|
\"")
|
|
80
134
|
```
|
|
81
135
|
|
|
82
|
-
|
|
136
|
+
#### "MCP 서버 관리"
|
|
137
|
+
|
|
138
|
+
먼저 현재 등록된 MCP 서버 목록을 수집:
|
|
83
139
|
|
|
84
140
|
```bash
|
|
85
|
-
|
|
86
|
-
|
|
141
|
+
Bash("claude mcp list 2>/dev/null")
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
결과를 파싱하여 테이블로 표시:
|
|
145
|
+
|
|
146
|
+
```markdown
|
|
147
|
+
## 등록된 MCP 서버
|
|
148
|
+
|
|
149
|
+
| # | 이름 | 상태 | 명령어 |
|
|
150
|
+
|---|------|------|--------|
|
|
151
|
+
| 1 | context7 | ✓ Connected | cmd /c npx -y @upstash/context7-mcp@latest |
|
|
152
|
+
| 2 | exa | ✓ Connected | cmd /c npx -y exa-mcp-server |
|
|
153
|
+
| 3 | powerpoint | ✓ Connected | uvx ppt-mcp |
|
|
154
|
+
| ... | ... | ... | ... |
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
그 후 AskUserQuestion:
|
|
158
|
+
```
|
|
159
|
+
question: "MCP 서버 관리 — 어떤 작업을 하시겠습니까?"
|
|
160
|
+
header: "MCP Servers ({connected}개 연결 / {failed}개 실패 / {disabled}개 비활성)"
|
|
161
|
+
options:
|
|
162
|
+
- label: "서버 제거"
|
|
163
|
+
description: "등록된 서버를 선택하여 제거"
|
|
164
|
+
- label: "서버 추가"
|
|
165
|
+
description: "새 MCP 서버 등록"
|
|
166
|
+
- label: "실패한 서버 재시작"
|
|
167
|
+
description: "연결 실패 서버를 재시작 시도"
|
|
168
|
+
- label: "뒤로"
|
|
169
|
+
description: "메인 메뉴로 돌아가기"
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
##### "서버 제거" 선택 시
|
|
173
|
+
|
|
174
|
+
AskUserQuestion (multiSelect):
|
|
175
|
+
```
|
|
176
|
+
question: "제거할 서버를 선택하세요"
|
|
177
|
+
header: "MCP 서버 제거"
|
|
178
|
+
options:
|
|
179
|
+
(등록된 서버를 각각 옵션으로 나열)
|
|
180
|
+
- label: "powerpoint"
|
|
181
|
+
description: "[local] uvx ppt-mcp — ✓ Connected"
|
|
182
|
+
- label: "stability-ai"
|
|
183
|
+
description: "[local] npx -y mcp-server-stability-ai — ✘ Failed"
|
|
184
|
+
...
|
|
185
|
+
multiSelect: true
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
선택된 서버들을 제거:
|
|
189
|
+
```bash
|
|
190
|
+
Bash("claude mcp remove '{서버명}' -s {scope}")
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
제거 후 결과 표시.
|
|
194
|
+
|
|
195
|
+
##### "서버 추가" 선택 시
|
|
196
|
+
|
|
197
|
+
AskUserQuestion:
|
|
198
|
+
```
|
|
199
|
+
question: "추가할 서버 유형을 선택하세요"
|
|
200
|
+
header: "MCP 서버 추가"
|
|
201
|
+
options:
|
|
202
|
+
- label: "stdio (npx/uvx)"
|
|
203
|
+
description: "npx, uvx 등 stdio 기반 서버"
|
|
204
|
+
- label: "SSE/HTTP"
|
|
205
|
+
description: "URL 기반 원격 서버"
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
stdio 선택 시 AskUserQuestion:
|
|
209
|
+
```
|
|
210
|
+
question: "서버 이름과 명령어를 입력하세요 (예: myserver -- cmd /c npx -y my-mcp-server)"
|
|
211
|
+
header: "stdio 서버 추가"
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
입력값을 파싱하여:
|
|
215
|
+
```bash
|
|
216
|
+
Bash("claude mcp add '{name}' -s local -- {command}")
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
SSE/HTTP 선택 시 AskUserQuestion:
|
|
220
|
+
```
|
|
221
|
+
question: "서버 이름과 URL을 입력하세요 (예: myserver http://localhost:8080/mcp)"
|
|
222
|
+
header: "HTTP 서버 추가"
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
입력값을 파싱하여:
|
|
226
|
+
```bash
|
|
227
|
+
Bash("claude mcp add --transport http '{name}' '{url}' -s local")
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
##### "실패한 서버 재시작" 선택 시
|
|
231
|
+
|
|
232
|
+
`claude mcp list` 결과에서 Failed 서버만 필터:
|
|
233
|
+
|
|
234
|
+
```
|
|
235
|
+
question: "재시작할 서버를 선택하세요"
|
|
236
|
+
header: "실패한 MCP 서버"
|
|
237
|
+
options:
|
|
238
|
+
(Failed 서버만 나열)
|
|
239
|
+
multiSelect: true
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
선택된 서버를 제거 후 재등록하여 재시작.
|
|
243
|
+
|
|
244
|
+
#### "파이프라인 조회"
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
Bash("curl -s http://127.0.0.1:27888/bridge/pipeline/list -X POST -H 'Content-Type: application/json' -d '{}' 2>/dev/null || echo '{\"error\":\"hub 미실행\"}'")
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
결과를 테이블로 표시.
|
|
251
|
+
|
|
252
|
+
#### "Assign 작업 조회"
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
Bash("curl -s http://127.0.0.1:27888/bridge/assign/status -X POST -H 'Content-Type: application/json' -d '{\"list\":true}' 2>/dev/null || echo '{\"error\":\"hub 미실행\"}'")
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
결과를 테이블로 표시. 실패 작업이 있으면 재시도 옵션 제공.
|
|
259
|
+
|
|
260
|
+
#### "DLQ 관리"
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
Bash("curl -s http://127.0.0.1:27888/status 2>/dev/null | node -e \"const d=JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')); console.log('DLQ depth: '+d.queues.dlq_depth)\"")
|
|
87
264
|
```
|
|
88
265
|
|
|
89
|
-
|
|
266
|
+
DLQ가 비어있으면 "DLQ가 비어있습니다." 표시.
|
|
267
|
+
내용이 있으면 AskUserQuestion:
|
|
268
|
+
```
|
|
269
|
+
question: "DLQ 처리 — 어떻게 하시겠습니까?"
|
|
270
|
+
header: "Dead Letter Queue ({count}건)"
|
|
271
|
+
options:
|
|
272
|
+
- label: "전체 조회"
|
|
273
|
+
description: "DLQ 메시지 목록 표시"
|
|
274
|
+
- label: "전체 재시도"
|
|
275
|
+
description: "모든 DLQ 메시지를 다시 큐에 넣기"
|
|
276
|
+
- label: "전체 삭제"
|
|
277
|
+
description: "DLQ 비우기"
|
|
278
|
+
- label: "뒤로"
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## fallthrough 라우팅
|
|
282
|
+
|
|
283
|
+
메인 메뉴 항목에 매칭되지 않는 자유형 입력은 hub 도메인 컨텍스트 범용 작업으로 처리:
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
# tfx-route.sh 경유 (권장)
|
|
287
|
+
Bash("bash ~/.claude/scripts/tfx-route.sh {에이전트} '{hub 컨텍스트 + 작업}' {mcp_profile}")
|
|
288
|
+
|
|
289
|
+
# Claude 네이티브 (탐색/검증)
|
|
290
|
+
Agent(subagent_type="oh-my-claudecode:explore", prompt="{작업}")
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
## CLI 등록 방법
|
|
90
294
|
|
|
91
295
|
허브 시작 후 각 CLI에 MCP 서버로 등록:
|
|
92
296
|
|
|
93
297
|
```bash
|
|
94
|
-
#
|
|
95
|
-
|
|
96
|
-
|
|
298
|
+
# Claude
|
|
299
|
+
claude mcp add --transport http tfx-hub http://127.0.0.1:27888/mcp
|
|
300
|
+
|
|
301
|
+
# Codex
|
|
97
302
|
codex mcp add tfx-hub --url http://127.0.0.1:27888/mcp
|
|
98
303
|
|
|
99
304
|
# Gemini (settings.json)
|
|
100
305
|
# mcpServers.tfx-hub.url = "http://127.0.0.1:27888/mcp"
|
|
101
|
-
|
|
102
|
-
# Claude
|
|
103
|
-
claude mcp add --transport http tfx-hub http://127.0.0.1:27888/mcp
|
|
104
306
|
```
|
|
105
307
|
|
|
106
|
-
## MCP 도구 (20개)
|
|
308
|
+
## MCP 도구 레퍼런스 (20개)
|
|
107
309
|
|
|
108
310
|
### Core — 기본 통신
|
|
109
311
|
|
|
@@ -150,14 +352,25 @@ claude mcp add --transport http tfx-hub http://127.0.0.1:27888/mcp
|
|
|
150
352
|
| `request_human_input` | 사용자 입력 요청 (CAPTCHA/승인/자격증명/선택/텍스트) |
|
|
151
353
|
| `submit_human_input` | 사용자 입력 응답 (accept/decline/cancel) |
|
|
152
354
|
|
|
153
|
-
##
|
|
355
|
+
## CLI 대응
|
|
154
356
|
|
|
155
|
-
|
|
|
156
|
-
|
|
157
|
-
|
|
|
158
|
-
|
|
|
159
|
-
|
|
|
160
|
-
|
|
|
357
|
+
| 스킬 UI | CLI 명령 |
|
|
358
|
+
|---------|---------|
|
|
359
|
+
| 허브 상태 보기 | `curl http://127.0.0.1:27888/status` |
|
|
360
|
+
| 허브 시작 | `node hub/server.mjs` |
|
|
361
|
+
| 허브 중지 | PID 파일에서 kill |
|
|
362
|
+
| MCP 서버 목록 | `claude mcp list` |
|
|
363
|
+
| MCP 서버 추가 | `claude mcp add ...` |
|
|
364
|
+
| MCP 서버 제거 | `claude mcp remove ...` |
|
|
365
|
+
|
|
366
|
+
## 에러 처리
|
|
367
|
+
|
|
368
|
+
| 상황 | 처리 |
|
|
369
|
+
|------|------|
|
|
370
|
+
| 허브 미실행 상태에서 조회 | "허브가 실행 중이 아닙니다. 시작하시겠습니까?" AskUserQuestion |
|
|
371
|
+
| curl 타임아웃 | "허브가 응답하지 않습니다. PID 파일을 확인하세요." |
|
|
372
|
+
| MCP 서버 추가 실패 | 에러 메시지 표시 + "다시 시도" 옵션 |
|
|
373
|
+
| PID 파일 없음 | "허브가 실행 중이 아닙니다." |
|
|
161
374
|
|
|
162
375
|
## 프로젝트 구조
|
|
163
376
|
|
|
@@ -170,41 +383,16 @@ hub/
|
|
|
170
383
|
├── hitl.mjs # Human-in-the-Loop 매니저
|
|
171
384
|
├── bridge.mjs # tfx-route.sh ↔ hub 브릿지 CLI
|
|
172
385
|
├── schema.sql # DB 스키마
|
|
173
|
-
├── paths.mjs # 경로 상수
|
|
174
|
-
├── pipe.mjs # Named Pipe 서버
|
|
175
|
-
├── assign-callbacks.mjs # assign job 콜백
|
|
176
|
-
├── intent.mjs # 인텐트 파싱
|
|
177
|
-
├── reflexion.mjs # reflexion 루프
|
|
178
|
-
├── research.mjs # 리서치 프록시
|
|
179
|
-
├── token-mode.mjs # 토큰 모드 관리
|
|
386
|
+
├── paths.mjs # 경로 상수
|
|
387
|
+
├── pipe.mjs # Named Pipe 서버
|
|
388
|
+
├── assign-callbacks.mjs # assign job 콜백
|
|
180
389
|
├── pipeline/ # 파이프라인 엔진
|
|
181
|
-
│ ├── index.mjs # createPipeline() 팩토리
|
|
182
|
-
│ ├── state.mjs # 파이프라인 상태 CRUD
|
|
183
|
-
│ ├── transitions.mjs # 전이 규칙
|
|
184
|
-
│ └── gates/ # HITL 게이트 (selfcheck, confidence)
|
|
185
390
|
├── delegator/ # 작업 위임 레이어
|
|
186
|
-
│ ├── index.mjs
|
|
187
|
-
│ ├── service.mjs
|
|
188
|
-
│ ├── contracts.mjs
|
|
189
|
-
│ └── tool-definitions.mjs
|
|
190
391
|
├── team/ # Claude Native Teams 통합
|
|
191
|
-
│ ├── nativeProxy.mjs # Teams MCP 프록시
|
|
192
|
-
│ ├── orchestrator.mjs # 팀 오케스트레이터
|
|
193
|
-
│ ├── session.mjs # 세션 관리
|
|
194
|
-
│ ├── dashboard.mjs # TUI 대시보드
|
|
195
|
-
│ ├── tui.mjs # TUI 렌더러
|
|
196
|
-
│ └── cli/ # tfx team CLI 커맨드
|
|
197
392
|
├── workers/ # CLI 워커 어댑터
|
|
198
|
-
|
|
199
|
-
│ ├── claude-worker.mjs
|
|
200
|
-
│ ├── codex-mcp.mjs
|
|
201
|
-
│ ├── gemini-worker.mjs
|
|
202
|
-
│ └── delegator-mcp.mjs
|
|
203
|
-
├── middleware/ # 요청 미들웨어
|
|
204
|
-
│ └── request-logger.mjs
|
|
393
|
+
├── middleware/ # 요청 미들웨어
|
|
205
394
|
├── quality/ # 품질 검사
|
|
206
|
-
|
|
207
|
-
└── public/ # 정적 자산 (대시보드 HTML, 트레이 아이콘)
|
|
395
|
+
└── public/ # 정적 자산
|
|
208
396
|
```
|
|
209
397
|
|
|
210
398
|
## 상태
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tfx-hub",
|
|
3
|
-
"description": "tfx-hub MCP 메시지 버스 관리.
|
|
3
|
+
"description": "tfx-hub MCP 메시지 버스 관리. AskUserQuestion 기반 인터랙티브 UI로 허브 시작/중지/상태확인, MCP 서버 관리, 에이전트 조회, 파이프라인 조회를 수행합니다. Use when: hub, 허브, 메시지 버스, message bus, 브릿지, bridge, MCP 서버 관리, 에이전트 통신",
|
|
4
4
|
"triggers": ["tfx-hub"],
|
|
5
|
-
"argument_hint": "<start|stop|status|자유형 작업 설명>"
|
|
5
|
+
"argument_hint": "<start|stop|status|mcp|자유형 작업 설명>"
|
|
6
6
|
}
|