triflux 10.9.21 → 10.9.22
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/config/mcp-registry.json +29 -0
- package/hub/account-broker.mjs +6 -4
- package/hub/cli-adapter-base.mjs +14 -14
- package/hub/lib/env-detect.mjs +47 -20
- package/hub/server.mjs +17 -15
- package/hub/team/headless.mjs +10 -0
- package/hub/team/swarm-hypervisor.mjs +2 -2
- package/hud/constants.mjs +24 -13
- package/hud/renderers.mjs +2 -1
- package/package.json +62 -21
- package/scripts/__tests__/keyword-detector.test.mjs +4 -4
- package/scripts/__tests__/release-governance.test.mjs +148 -0
- package/scripts/doctor-diagnose.mjs +6 -7
- package/scripts/lib/cross-review-utils.mjs +2 -2
- package/scripts/lib/mcp-filter.mjs +9 -5
- package/scripts/release/bump-version.mjs +77 -0
- package/scripts/release/check-sync.mjs +51 -0
- package/scripts/release/lib.mjs +303 -0
- package/scripts/release/prepare.mjs +85 -0
- package/scripts/release/publish.mjs +87 -0
- package/scripts/release/verify.mjs +81 -0
- package/scripts/release/version-manifest.json +26 -0
- package/scripts/remote-spawn.mjs +3 -3
- package/scripts/setup.mjs +18 -15
- package/scripts/tfx-route.sh +64 -8
- 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 +339 -0
- package/tui/setup.mjs +598 -0
- package/CLAUDE.md +0 -212
- 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.9.21",
|
|
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.9.21"
|
|
34
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "triflux",
|
|
3
|
+
"version": "10.9.22",
|
|
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
|
+
}
|
|
@@ -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/hub/account-broker.mjs
CHANGED
|
@@ -120,11 +120,13 @@ class AccountBroker extends EventEmitter {
|
|
|
120
120
|
#config;
|
|
121
121
|
#state; // Map<accountId, accountState>
|
|
122
122
|
#roundRobinIndex; // Map<provider, number>
|
|
123
|
+
#persist; // boolean — disable persistence for tests
|
|
123
124
|
|
|
124
|
-
constructor(config) {
|
|
125
|
+
constructor(config, { _skipPersistence = false } = {}) {
|
|
125
126
|
super();
|
|
126
127
|
const parsed = ConfigSchema.parse(config);
|
|
127
128
|
this.#config = parsed;
|
|
129
|
+
this.#persist = !_skipPersistence;
|
|
128
130
|
|
|
129
131
|
this.#state = new Map();
|
|
130
132
|
this.#roundRobinIndex = new Map();
|
|
@@ -134,7 +136,7 @@ class AccountBroker extends EventEmitter {
|
|
|
134
136
|
...(parsed.gemini || []).map((a) => ({ ...a, provider: "gemini" })),
|
|
135
137
|
];
|
|
136
138
|
|
|
137
|
-
const persisted = loadPersistedState();
|
|
139
|
+
const persisted = this.#persist ? loadPersistedState() : null;
|
|
138
140
|
const pEntries = persisted?.entries || {};
|
|
139
141
|
|
|
140
142
|
for (const account of allAccounts) {
|
|
@@ -386,7 +388,7 @@ class AccountBroker extends EventEmitter {
|
|
|
386
388
|
};
|
|
387
389
|
|
|
388
390
|
this.#state.set(accountId, updated);
|
|
389
|
-
persistState(this.#state);
|
|
391
|
+
if (this.#persist) persistState(this.#state);
|
|
390
392
|
this.emit("release", { id: accountId, ok });
|
|
391
393
|
}
|
|
392
394
|
|
|
@@ -401,7 +403,7 @@ class AccountBroker extends EventEmitter {
|
|
|
401
403
|
leasedAt: null,
|
|
402
404
|
cooldownUntil: Date.now() + coolMs,
|
|
403
405
|
});
|
|
404
|
-
persistState(this.#state);
|
|
406
|
+
if (this.#persist) persistState(this.#state);
|
|
405
407
|
}
|
|
406
408
|
|
|
407
409
|
// ── snapshot ──────────────────────────────────────────────────
|
package/hub/cli-adapter-base.mjs
CHANGED
|
@@ -218,14 +218,15 @@ export async function executeWithCircuitBroker({
|
|
|
218
218
|
const { withRetry } = await import("./workers/worker-utils.mjs");
|
|
219
219
|
|
|
220
220
|
// access broker as live binding property (not destructured) so reloadBroker() propagates
|
|
221
|
-
const
|
|
222
|
-
|
|
221
|
+
const hasBroker = brokerMod.broker != null;
|
|
222
|
+
const lease = hasBroker ? brokerMod.broker.lease({ provider }) : null;
|
|
223
|
+
if (hasBroker && !lease) {
|
|
223
224
|
return createResult(false, { fellBack: true, failureMode: "circuit_open" });
|
|
224
225
|
}
|
|
225
226
|
|
|
226
227
|
const preflight = await preflightFn(opts);
|
|
227
228
|
if (!preflight.ok) {
|
|
228
|
-
brokerMod.broker.release(lease.id, { ok: false });
|
|
229
|
+
if (lease) brokerMod.broker.release(lease.id, { ok: false });
|
|
229
230
|
return createResult(false, {
|
|
230
231
|
stderr: appendWarnings("", preflight.warnings),
|
|
231
232
|
fellBack: opts.fallbackToClaude !== false,
|
|
@@ -273,20 +274,19 @@ export async function executeWithCircuitBroker({
|
|
|
273
274
|
}
|
|
274
275
|
|
|
275
276
|
if (lastResult.ok) {
|
|
276
|
-
brokerMod.broker.release(lease.id, { ok: true });
|
|
277
|
+
if (lease) brokerMod.broker.release(lease.id, { ok: true });
|
|
277
278
|
return lastResult;
|
|
278
279
|
}
|
|
279
280
|
|
|
280
|
-
if (
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
brokerMod.broker.release(lease.id, { ok: false });
|
|
281
|
+
if (lease) {
|
|
282
|
+
if (lastResult.failureMode === "rate_limited") {
|
|
283
|
+
const text = `${lastResult.output || ""}\n${lastResult.stderr || ""}`;
|
|
284
|
+
const coolMs = parseRetryAfterMs(text, provider);
|
|
285
|
+
brokerMod.broker.markRateLimited(lease.id, coolMs);
|
|
286
|
+
brokerMod.broker.emit("cooldown", { id: lease.id, provider, coolMs, reason: "quota_exhausted" });
|
|
287
|
+
} else {
|
|
288
|
+
brokerMod.broker.release(lease.id, { ok: false });
|
|
289
|
+
}
|
|
290
290
|
}
|
|
291
291
|
return {
|
|
292
292
|
...lastResult,
|
package/hub/lib/env-detect.mjs
CHANGED
|
@@ -3,68 +3,95 @@ import { execFileSync } from "node:child_process";
|
|
|
3
3
|
import { platform as osPlatform } from "node:os";
|
|
4
4
|
|
|
5
5
|
let _cached = null;
|
|
6
|
+
let _shellCache = null;
|
|
7
|
+
let _terminalCache = null;
|
|
8
|
+
let _multiplexerCache = null;
|
|
9
|
+
|
|
10
|
+
const PIPE_OPTS = { encoding: "utf8", timeout: 3000, stdio: "pipe" };
|
|
6
11
|
|
|
7
12
|
/**
|
|
8
13
|
* 기본 쉘 감지
|
|
9
|
-
* Windows: pwsh → powershell
|
|
10
|
-
* Unix: $SHELL → /bin/sh
|
|
14
|
+
* Windows: pwsh → powershell (full path + version)
|
|
15
|
+
* Unix: $SHELL → /bin/sh (+ version)
|
|
11
16
|
*/
|
|
12
17
|
export function detectShell() {
|
|
18
|
+
if (_shellCache) return _shellCache;
|
|
19
|
+
|
|
13
20
|
const platform = osPlatform();
|
|
21
|
+
|
|
14
22
|
if (platform === "win32") {
|
|
15
23
|
try {
|
|
16
|
-
execFileSync("where", ["pwsh.exe"],
|
|
17
|
-
|
|
24
|
+
const path = execFileSync("where", ["pwsh.exe"], PIPE_OPTS).trim().split(/\r?\n/)[0];
|
|
25
|
+
let version = null;
|
|
26
|
+
try {
|
|
27
|
+
version = execFileSync(path, ["-NoLogo", "-NoProfile", "-Command", "$PSVersionTable.PSVersion.ToString()"], PIPE_OPTS).trim();
|
|
28
|
+
} catch {}
|
|
29
|
+
_shellCache = { name: "pwsh", path, version };
|
|
18
30
|
} catch {
|
|
19
31
|
try {
|
|
20
|
-
execFileSync("where", ["powershell.exe"],
|
|
21
|
-
|
|
32
|
+
const path = execFileSync("where", ["powershell.exe"], PIPE_OPTS).trim().split(/\r?\n/)[0];
|
|
33
|
+
_shellCache = { name: "powershell", path, version: null };
|
|
22
34
|
} catch {
|
|
23
|
-
|
|
35
|
+
_shellCache = { name: "powershell", path: "", version: null, installHint: "pwsh: winget install Microsoft.PowerShell" };
|
|
24
36
|
}
|
|
25
37
|
}
|
|
38
|
+
return _shellCache;
|
|
26
39
|
}
|
|
27
40
|
|
|
28
41
|
const shellPath = process.env.SHELL || "/bin/sh";
|
|
29
42
|
const name = shellPath.split("/").pop() || "sh";
|
|
30
|
-
|
|
43
|
+
let version = null;
|
|
44
|
+
try {
|
|
45
|
+
version = execFileSync(shellPath, ["--version"], PIPE_OPTS).trim();
|
|
46
|
+
} catch {}
|
|
47
|
+
_shellCache = { name, path: shellPath, version };
|
|
48
|
+
return _shellCache;
|
|
31
49
|
}
|
|
32
50
|
|
|
33
51
|
/**
|
|
34
52
|
* 터미널 에뮬레이터 감지
|
|
35
53
|
*/
|
|
36
54
|
export function detectTerminal() {
|
|
55
|
+
if (_terminalCache) return _terminalCache;
|
|
56
|
+
|
|
37
57
|
const platform = osPlatform();
|
|
38
58
|
if (platform === "win32") {
|
|
39
59
|
try {
|
|
40
|
-
execFileSync("where", ["wt.exe"],
|
|
41
|
-
|
|
60
|
+
execFileSync("where", ["wt.exe"], PIPE_OPTS);
|
|
61
|
+
_terminalCache = { name: "windows-terminal", hasWt: true };
|
|
42
62
|
} catch {
|
|
43
|
-
|
|
63
|
+
_terminalCache = { name: "unknown", hasWt: false, installHint: "wt: winget install Microsoft.WindowsTerminal" };
|
|
44
64
|
}
|
|
65
|
+
return _terminalCache;
|
|
45
66
|
}
|
|
46
67
|
|
|
47
68
|
if (process.env.TERM_PROGRAM === "iTerm.app") {
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
|
|
69
|
+
_terminalCache = { name: "iterm2", hasWt: false };
|
|
70
|
+
} else if (process.env.TERM_PROGRAM === "Apple_Terminal") {
|
|
71
|
+
_terminalCache = { name: "terminal-app", hasWt: false };
|
|
72
|
+
} else {
|
|
73
|
+
_terminalCache = { name: "unknown", hasWt: false };
|
|
52
74
|
}
|
|
53
|
-
|
|
54
|
-
return { name: "unknown", hasWt: false };
|
|
75
|
+
return _terminalCache;
|
|
55
76
|
}
|
|
56
77
|
|
|
57
78
|
/**
|
|
58
79
|
* 멀티플렉서 감지 (tmux)
|
|
59
80
|
*/
|
|
60
81
|
export function detectMultiplexer() {
|
|
82
|
+
if (_multiplexerCache) return _multiplexerCache;
|
|
83
|
+
|
|
61
84
|
try {
|
|
62
85
|
const cmd = osPlatform() === "win32" ? "where" : "which";
|
|
63
|
-
const path = execFileSync(cmd, ["tmux"],
|
|
64
|
-
|
|
86
|
+
const path = execFileSync(cmd, ["tmux"], PIPE_OPTS).trim();
|
|
87
|
+
_multiplexerCache = { name: "tmux", path };
|
|
65
88
|
} catch {
|
|
66
|
-
|
|
89
|
+
const hint = osPlatform() === "win32"
|
|
90
|
+
? "tmux: install tmux in WSL or MSYS2"
|
|
91
|
+
: undefined;
|
|
92
|
+
_multiplexerCache = { name: "none", path: null, ...(hint ? { installHint: hint } : {}) };
|
|
67
93
|
}
|
|
94
|
+
return _multiplexerCache;
|
|
68
95
|
}
|
|
69
96
|
|
|
70
97
|
/**
|
package/hub/server.mjs
CHANGED
|
@@ -80,7 +80,7 @@ const AIMD_WINDOW_MS = 30 * 60 * 1000;
|
|
|
80
80
|
const AIMD_INITIAL_BATCH_SIZE = 3;
|
|
81
81
|
const AIMD_MIN_BATCH_SIZE = 1;
|
|
82
82
|
const AIMD_MAX_BATCH_SIZE = 10;
|
|
83
|
-
const HUB_IDLE_TIMEOUT_DEFAULT_MS =
|
|
83
|
+
const HUB_IDLE_TIMEOUT_DEFAULT_MS = 0; // 0 = 영구 실행 (idle shutdown 비활성). TFX_HUB_IDLE_TIMEOUT_MS 환경변수로 오버라이드 가능
|
|
84
84
|
const HUB_IDLE_SWEEP_DEFAULT_MS = 60 * 1000;
|
|
85
85
|
const STATIC_CONTENT_TYPES = Object.freeze({
|
|
86
86
|
".html": "text/html",
|
|
@@ -1655,21 +1655,23 @@ export async function startHub({
|
|
|
1655
1655
|
return stopPromise;
|
|
1656
1656
|
};
|
|
1657
1657
|
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
void stopFn().catch((error) => {
|
|
1666
|
-
hubLog.error(
|
|
1667
|
-
{ err: error, idleMs, idleTimeoutMs: hubIdleTimeoutMs, port },
|
|
1668
|
-
"hub.idle_timeout_shutdown_failed",
|
|
1658
|
+
if (hubIdleTimeoutMs > 0) {
|
|
1659
|
+
idleTimer = setInterval(() => {
|
|
1660
|
+
const idleMs = Date.now() - lastRequestAt;
|
|
1661
|
+
if (idleMs < hubIdleTimeoutMs) return;
|
|
1662
|
+
hubLog.warn(
|
|
1663
|
+
{ idleMs, idleTimeoutMs: hubIdleTimeoutMs, port },
|
|
1664
|
+
"hub.idle_timeout_shutdown",
|
|
1669
1665
|
);
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1666
|
+
void stopFn().catch((error) => {
|
|
1667
|
+
hubLog.error(
|
|
1668
|
+
{ err: error, idleMs, idleTimeoutMs: hubIdleTimeoutMs, port },
|
|
1669
|
+
"hub.idle_timeout_shutdown_failed",
|
|
1670
|
+
);
|
|
1671
|
+
});
|
|
1672
|
+
}, hubIdleSweepMs);
|
|
1673
|
+
idleTimer.unref();
|
|
1674
|
+
}
|
|
1673
1675
|
|
|
1674
1676
|
resolveHub({
|
|
1675
1677
|
reused: false,
|
package/hub/team/headless.mjs
CHANGED
|
@@ -161,6 +161,16 @@ const MCP_PROFILE_HINTS = {
|
|
|
161
161
|
* @param {string} [opts.contextFile] — 컨텍스트 파일 경로 (최대 32KB, UTF-8 안전 절단)
|
|
162
162
|
* @returns {string} PowerShell 명령
|
|
163
163
|
*/
|
|
164
|
+
// ── Dashboard attach args for WT ────────────────────────────────
|
|
165
|
+
|
|
166
|
+
export function buildDashboardAttachArgs(sessionName, layout, workerCount, anchor = "window") {
|
|
167
|
+
const safeName = String(sessionName).replace(/[^a-zA-Z0-9_-]/g, "");
|
|
168
|
+
const base = anchor === "tab"
|
|
169
|
+
? ["-w", "0", "nt"]
|
|
170
|
+
: ["-w", "new"];
|
|
171
|
+
return [...base, "--session", safeName, "--layout", layout, "--workers", String(workerCount)];
|
|
172
|
+
}
|
|
173
|
+
|
|
164
174
|
export function buildHeadlessCommand(cli, prompt, resultFile, opts = {}) {
|
|
165
175
|
const { handoff = true, mcp, contextFile, model, cwd } = opts;
|
|
166
176
|
const resolvedCli = resolveCliType(cli);
|
|
@@ -1065,10 +1065,10 @@ export function createSwarmHypervisor(opts) {
|
|
|
1065
1065
|
* @param {SwarmPlan} swarmPlan — from planSwarm()
|
|
1066
1066
|
* @returns {Promise<SwarmStatus>}
|
|
1067
1067
|
*/
|
|
1068
|
-
/** Hub keepalive —
|
|
1068
|
+
/** Hub keepalive — Hub crash recovery (idle timeout은 기본 비활성이므로 crash 복구 용도) */
|
|
1069
1069
|
let hubKeepaliveTimer = null;
|
|
1070
1070
|
function startHubKeepalive() {
|
|
1071
|
-
// 5분마다 Hub /status 핑
|
|
1071
|
+
// 5분마다 Hub /status 핑 — crash 감지 시 ensureHubAlive로 재시작
|
|
1072
1072
|
hubKeepaliveTimer = setInterval(
|
|
1073
1073
|
async () => {
|
|
1074
1074
|
try {
|
package/hud/constants.mjs
CHANGED
|
@@ -8,20 +8,23 @@ export const VERSION = "2.0";
|
|
|
8
8
|
|
|
9
9
|
export const QOS_PATH = join(
|
|
10
10
|
homedir(),
|
|
11
|
-
".
|
|
12
|
-
"
|
|
11
|
+
".claude",
|
|
12
|
+
"cache",
|
|
13
|
+
"tfx-hub",
|
|
13
14
|
"cli_qos_profile.json",
|
|
14
15
|
);
|
|
15
16
|
export const ACCOUNTS_CONFIG_PATH = join(
|
|
16
17
|
homedir(),
|
|
17
|
-
".
|
|
18
|
-
"
|
|
18
|
+
".claude",
|
|
19
|
+
"cache",
|
|
20
|
+
"tfx-hub",
|
|
19
21
|
"accounts.json",
|
|
20
22
|
);
|
|
21
23
|
export const ACCOUNTS_STATE_PATH = join(
|
|
22
24
|
homedir(),
|
|
23
|
-
".
|
|
24
|
-
"
|
|
25
|
+
".claude",
|
|
26
|
+
"cache",
|
|
27
|
+
"tfx-hub",
|
|
25
28
|
"cli_accounts_state.json",
|
|
26
29
|
);
|
|
27
30
|
|
|
@@ -46,7 +49,13 @@ export const CONTEXT_MONITOR_LEGACY_PATH = join(
|
|
|
46
49
|
"state",
|
|
47
50
|
"context-monitor.json",
|
|
48
51
|
);
|
|
49
|
-
export const CONTEXT_MONITOR_LOG_DIR = join(
|
|
52
|
+
export const CONTEXT_MONITOR_LOG_DIR = join(
|
|
53
|
+
homedir(),
|
|
54
|
+
".claude",
|
|
55
|
+
"cache",
|
|
56
|
+
"tfx-hub",
|
|
57
|
+
"logs",
|
|
58
|
+
);
|
|
50
59
|
|
|
51
60
|
// 원격 프로브 캐시 (tfx-remote-spawn)
|
|
52
61
|
export const REMOTE_ENV_CACHE_DIR = join(
|
|
@@ -70,15 +79,17 @@ export const CLAUDE_USAGE_CACHE_PATH = join(
|
|
|
70
79
|
"cache",
|
|
71
80
|
"claude-usage-cache.json",
|
|
72
81
|
);
|
|
73
|
-
export const
|
|
82
|
+
export const PLUGIN_USAGE_CACHE_PATH = join(
|
|
74
83
|
homedir(),
|
|
75
84
|
".claude",
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
"
|
|
85
|
+
"cache",
|
|
86
|
+
"tfx-hub",
|
|
87
|
+
"plugin-usage-cache.json",
|
|
79
88
|
);
|
|
80
|
-
export const
|
|
81
|
-
export const
|
|
89
|
+
export const OMC_PLUGIN_USAGE_CACHE_PATH = PLUGIN_USAGE_CACHE_PATH; // OMC alias
|
|
90
|
+
export const CLAUDE_USAGE_STALE_MS_SOLO = 5 * 60 * 1000; // 플러그인 없을 때: 5분 캐시
|
|
91
|
+
export const CLAUDE_USAGE_STALE_MS_WITH_PLUGIN = 15 * 60 * 1000; // 플러그인 있을 때: 15분
|
|
92
|
+
export const CLAUDE_USAGE_STALE_MS_WITH_OMC = CLAUDE_USAGE_STALE_MS_WITH_PLUGIN; // OMC alias
|
|
82
93
|
export const CLAUDE_USAGE_429_BACKOFF_MS = 10 * 60 * 1000; // 429 에러 시 10분 backoff
|
|
83
94
|
export const GEMINI_429_BASE_DELAY_MS = 2000;
|
|
84
95
|
export const GEMINI_429_MAX_RETRIES = 3;
|
package/hud/renderers.mjs
CHANGED
|
@@ -389,7 +389,8 @@ export function getClaudeRows(
|
|
|
389
389
|
}
|
|
390
390
|
|
|
391
391
|
if (currentTier === "minimal") {
|
|
392
|
-
const
|
|
392
|
+
const staleTag = claudeUsage?.stale ? ` ${dim("[stale]")}` : "";
|
|
393
|
+
const quotaSection = `${dim("5h:")}${fStr} ${dim("1w:")}${wStr}${staleTag}`;
|
|
393
394
|
const right = `${dim("CTX:")}${colorByPercent(ctxView.percent, ctxView.display)}`;
|
|
394
395
|
return [{ prefix, left: quotaSection, right }];
|
|
395
396
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "triflux",
|
|
3
|
-
"version": "10.9.
|
|
3
|
+
"version": "10.9.22",
|
|
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,75 @@
|
|
|
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:bump": "node scripts/release/bump-version.mjs",
|
|
59
|
+
"release:prepare": "node scripts/release/prepare.mjs",
|
|
60
|
+
"release:publish": "node scripts/release/publish.mjs",
|
|
61
|
+
"release:verify": "node scripts/release/verify.mjs"
|
|
62
|
+
},
|
|
63
|
+
"engines": {
|
|
64
|
+
"node": ">=18.0.0"
|
|
65
|
+
},
|
|
66
|
+
"repository": {
|
|
67
|
+
"type": "git",
|
|
68
|
+
"url": "git+https://github.com/tellang/triflux.git"
|
|
69
|
+
},
|
|
70
|
+
"homepage": "https://github.com/tellang/triflux#readme",
|
|
71
|
+
"author": "tellang",
|
|
72
|
+
"license": "MIT",
|
|
73
|
+
"dependencies": {
|
|
74
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
75
|
+
"better-sqlite3": "^12.6.2",
|
|
76
|
+
"pino": "^10.3.1",
|
|
77
|
+
"pino-pretty": "^13.1.3",
|
|
78
|
+
"systray2": "^2.1.4",
|
|
79
|
+
"zod": "^4.0.0"
|
|
80
|
+
},
|
|
81
|
+
"devDependencies": {
|
|
82
|
+
"@biomejs/biome": "^2.0.0",
|
|
83
|
+
"knip": "^6.3.0"
|
|
84
|
+
},
|
|
36
85
|
"keywords": [
|
|
37
86
|
"claude-code",
|
|
38
87
|
"plugin",
|
|
@@ -43,13 +92,5 @@
|
|
|
43
92
|
"multi-model",
|
|
44
93
|
"triflux",
|
|
45
94
|
"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
|
-
}
|
|
95
|
+
]
|
|
55
96
|
}
|
|
@@ -34,7 +34,7 @@ const { extractPrompt, sanitizeForKeywordDetection } = detectorModule;
|
|
|
34
34
|
|
|
35
35
|
function loadCompiledRules() {
|
|
36
36
|
const rules = loadRules(rulesPath);
|
|
37
|
-
assert.
|
|
37
|
+
assert.ok(rules.length >= 32);
|
|
38
38
|
return compileRules(rules);
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -108,8 +108,8 @@ test("sanitizeForKeywordDetection: 코드블록/URL/파일경로/XML 태그 제
|
|
|
108
108
|
|
|
109
109
|
test("loadRules: 유효한 JSON 로드", () => {
|
|
110
110
|
const rules = loadRules(rulesPath);
|
|
111
|
-
assert.
|
|
112
|
-
assert.
|
|
111
|
+
assert.ok(rules.length >= 32);
|
|
112
|
+
assert.ok(rules.filter((rule) => rule.skill).length >= 18);
|
|
113
113
|
assert.equal(rules.filter((rule) => rule.mcp_route).length, 10);
|
|
114
114
|
});
|
|
115
115
|
|
|
@@ -130,7 +130,7 @@ test("loadRules: 잘못된 파일 처리", () => {
|
|
|
130
130
|
test("compileRules: 정규식 컴파일 성공", () => {
|
|
131
131
|
const rules = loadRules(rulesPath);
|
|
132
132
|
const compiled = compileRules(rules);
|
|
133
|
-
assert.equal(compiled.length,
|
|
133
|
+
assert.equal(compiled.length, rules.length);
|
|
134
134
|
for (const rule of compiled) {
|
|
135
135
|
assert.ok(Array.isArray(rule.compiledPatterns));
|
|
136
136
|
assert.ok(rule.compiledPatterns.length > 0);
|