triflux 4.0.0 → 4.0.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 +20 -8
- package/hub/bridge.mjs +52 -0
- package/package.json +61 -61
package/bin/triflux.mjs
CHANGED
|
@@ -1570,13 +1570,24 @@ function cmdUpdate() {
|
|
|
1570
1570
|
if (stoppedHubInfo?.pid) {
|
|
1571
1571
|
info(`실행 중 hub 정지 (PID ${stoppedHubInfo.pid})`);
|
|
1572
1572
|
}
|
|
1573
|
-
const npmCmd = isDev ? "npm install -g triflux@dev" : "npm
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1573
|
+
const npmCmd = isDev ? "npm install -g triflux@dev" : "npm install -g triflux@latest";
|
|
1574
|
+
let result;
|
|
1575
|
+
try {
|
|
1576
|
+
result = execSync(npmCmd, {
|
|
1577
|
+
encoding: "utf8",
|
|
1578
|
+
timeout: 90000,
|
|
1579
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1580
|
+
}).trim().split(/\r?\n/)[0];
|
|
1581
|
+
} catch (retryErr) {
|
|
1582
|
+
// Windows: 자기 자신의 파일 잠금으로 첫 시도 실패 가능 → 1회 재시도
|
|
1583
|
+
info("첫 시도 실패, 재시도 중...");
|
|
1584
|
+
result = execSync(npmCmd, {
|
|
1585
|
+
encoding: "utf8",
|
|
1586
|
+
timeout: 90000,
|
|
1587
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1588
|
+
}).trim().split(/\r?\n/)[0];
|
|
1589
|
+
}
|
|
1590
|
+
ok(`${npmCmd} — ${result || "완료"}`);
|
|
1580
1591
|
updated = true;
|
|
1581
1592
|
break;
|
|
1582
1593
|
}
|
|
@@ -1611,7 +1622,8 @@ function cmdUpdate() {
|
|
|
1611
1622
|
if (stoppedHubInfo && startHubAfterUpdate(stoppedHubInfo)) {
|
|
1612
1623
|
info("업데이트 실패 후 hub 재기동 시도");
|
|
1613
1624
|
}
|
|
1614
|
-
|
|
1625
|
+
const stderr = e.stderr?.toString().trim();
|
|
1626
|
+
fail(`업데이트 실패: ${e.message}${stderr ? `\n ${stderr.split(/\r?\n/)[0]}` : ""}`);
|
|
1615
1627
|
return;
|
|
1616
1628
|
}
|
|
1617
1629
|
|
package/hub/bridge.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import net from 'node:net';
|
|
|
8
8
|
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
9
9
|
import { join } from 'node:path';
|
|
10
10
|
import { homedir } from 'node:os';
|
|
11
|
+
import { spawn } from 'node:child_process';
|
|
11
12
|
import { parseArgs as nodeParseArgs } from 'node:util';
|
|
12
13
|
import { randomUUID } from 'node:crypto';
|
|
13
14
|
import { fileURLToPath } from 'node:url';
|
|
@@ -284,6 +285,37 @@ export function parseJsonSafe(raw, fallback = null) {
|
|
|
284
285
|
}
|
|
285
286
|
}
|
|
286
287
|
|
|
288
|
+
// Hub 자동 재시작 (Pipe+HTTP 모두 실패 시 1회 시도, 최대 4초 대기)
|
|
289
|
+
async function tryRestartHub() {
|
|
290
|
+
const serverPath = join(PROJECT_ROOT, 'hub', 'server.mjs');
|
|
291
|
+
if (!existsSync(serverPath)) return false;
|
|
292
|
+
|
|
293
|
+
try {
|
|
294
|
+
const child = spawn(process.execPath, [serverPath], {
|
|
295
|
+
detached: true,
|
|
296
|
+
stdio: 'ignore',
|
|
297
|
+
windowsHide: true,
|
|
298
|
+
});
|
|
299
|
+
child.unref();
|
|
300
|
+
} catch {
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
for (let i = 0; i < 8; i++) {
|
|
305
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
306
|
+
try {
|
|
307
|
+
const res = await fetch(`${getHubUrl()}/status`, {
|
|
308
|
+
signal: AbortSignal.timeout(1000),
|
|
309
|
+
});
|
|
310
|
+
if (res.ok) {
|
|
311
|
+
const data = await res.json();
|
|
312
|
+
if (data?.hub?.state === 'healthy') return true;
|
|
313
|
+
}
|
|
314
|
+
} catch {}
|
|
315
|
+
}
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
|
|
287
319
|
async function requestHub(operation, body, timeoutMs = 3000, fallback = null) {
|
|
288
320
|
const viaPipe = operation.transport === 'command'
|
|
289
321
|
? await pipeCommand(operation.action, body, timeoutMs)
|
|
@@ -303,6 +335,26 @@ async function requestHub(operation, body, timeoutMs = 3000, fallback = null) {
|
|
|
303
335
|
return { transport: 'http', result: viaHttp };
|
|
304
336
|
}
|
|
305
337
|
|
|
338
|
+
// Hub 재시작 시도 → Pipe/HTTP 재시도
|
|
339
|
+
if (await tryRestartHub()) {
|
|
340
|
+
const retryPipe = operation.transport === 'command'
|
|
341
|
+
? await pipeCommand(operation.action, body, timeoutMs)
|
|
342
|
+
: await pipeQuery(operation.action, body, timeoutMs);
|
|
343
|
+
if (retryPipe) {
|
|
344
|
+
return { transport: 'pipe', result: retryPipe };
|
|
345
|
+
}
|
|
346
|
+
const retryHttp = operation.httpPath
|
|
347
|
+
? await requestJson(operation.httpPath, {
|
|
348
|
+
method: operation.httpMethod || 'POST',
|
|
349
|
+
body: operation.httpMethod === 'GET' ? undefined : body,
|
|
350
|
+
timeoutMs: Math.max(timeoutMs, 5000),
|
|
351
|
+
})
|
|
352
|
+
: null;
|
|
353
|
+
if (retryHttp) {
|
|
354
|
+
return { transport: 'http', result: retryHttp };
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
306
358
|
if (!fallback) return null;
|
|
307
359
|
const viaFallback = await fallback();
|
|
308
360
|
if (!viaFallback) return null;
|
package/package.json
CHANGED
|
@@ -1,61 +1,61 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "triflux",
|
|
3
|
-
"version": "4.0.
|
|
4
|
-
"description": "CLI-first multi-model orchestrator for Claude Code — route tasks to Codex, Gemini, and Claude",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"bin": {
|
|
7
|
-
"triflux": "bin/triflux.mjs",
|
|
8
|
-
"tfx": "bin/triflux.mjs",
|
|
9
|
-
"tfl": "bin/triflux.mjs",
|
|
10
|
-
"tfx-setup": "bin/tfx-setup.mjs",
|
|
11
|
-
"tfx-doctor": "bin/tfx-doctor.mjs"
|
|
12
|
-
},
|
|
13
|
-
"files": [
|
|
14
|
-
"bin",
|
|
15
|
-
"hub",
|
|
16
|
-
"skills",
|
|
17
|
-
"!**/failure-reports",
|
|
18
|
-
"scripts",
|
|
19
|
-
"hooks",
|
|
20
|
-
"hud",
|
|
21
|
-
".claude-plugin",
|
|
22
|
-
".mcp.json",
|
|
23
|
-
"README.md",
|
|
24
|
-
"README.ko.md",
|
|
25
|
-
"LICENSE"
|
|
26
|
-
],
|
|
27
|
-
"scripts": {
|
|
28
|
-
"setup": "node scripts/setup.mjs",
|
|
29
|
-
"postinstall": "node scripts/setup.mjs",
|
|
30
|
-
"test": "node --test --test-force-exit --test-concurrency=1 \"tests/**/*.test.mjs\" \"scripts/__tests__/**/*.test.mjs\"",
|
|
31
|
-
"test:unit": "node --test --test-force-exit --test-concurrency=1 tests/unit/**/*.test.mjs",
|
|
32
|
-
"test:integration": "node --test --test-force-exit --test-concurrency=1 tests/integration/**/*.test.mjs",
|
|
33
|
-
"test:route-smoke": "node --test scripts/test-tfx-route-no-claude-native.mjs"
|
|
34
|
-
},
|
|
35
|
-
"engines": {
|
|
36
|
-
"node": ">=18.0.0"
|
|
37
|
-
},
|
|
38
|
-
"repository": {
|
|
39
|
-
"type": "git",
|
|
40
|
-
"url": "git+https://github.com/tellang/triflux.git"
|
|
41
|
-
},
|
|
42
|
-
"homepage": "https://github.com/tellang/triflux#readme",
|
|
43
|
-
"author": "tellang",
|
|
44
|
-
"license": "MIT",
|
|
45
|
-
"dependencies": {
|
|
46
|
-
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
47
|
-
"better-sqlite3": "^12.6.2",
|
|
48
|
-
"systray2": "^2.1.4"
|
|
49
|
-
},
|
|
50
|
-
"keywords": [
|
|
51
|
-
"claude-code",
|
|
52
|
-
"plugin",
|
|
53
|
-
"codex",
|
|
54
|
-
"gemini",
|
|
55
|
-
"cli-routing",
|
|
56
|
-
"orchestration",
|
|
57
|
-
"multi-model",
|
|
58
|
-
"triflux",
|
|
59
|
-
"tfx"
|
|
60
|
-
]
|
|
61
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "triflux",
|
|
3
|
+
"version": "4.0.2",
|
|
4
|
+
"description": "CLI-first multi-model orchestrator for Claude Code — route tasks to Codex, Gemini, and Claude",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"triflux": "bin/triflux.mjs",
|
|
8
|
+
"tfx": "bin/triflux.mjs",
|
|
9
|
+
"tfl": "bin/triflux.mjs",
|
|
10
|
+
"tfx-setup": "bin/tfx-setup.mjs",
|
|
11
|
+
"tfx-doctor": "bin/tfx-doctor.mjs"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"bin",
|
|
15
|
+
"hub",
|
|
16
|
+
"skills",
|
|
17
|
+
"!**/failure-reports",
|
|
18
|
+
"scripts",
|
|
19
|
+
"hooks",
|
|
20
|
+
"hud",
|
|
21
|
+
".claude-plugin",
|
|
22
|
+
".mcp.json",
|
|
23
|
+
"README.md",
|
|
24
|
+
"README.ko.md",
|
|
25
|
+
"LICENSE"
|
|
26
|
+
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"setup": "node scripts/setup.mjs",
|
|
29
|
+
"postinstall": "node scripts/setup.mjs",
|
|
30
|
+
"test": "node --test --test-force-exit --test-concurrency=1 \"tests/**/*.test.mjs\" \"scripts/__tests__/**/*.test.mjs\"",
|
|
31
|
+
"test:unit": "node --test --test-force-exit --test-concurrency=1 tests/unit/**/*.test.mjs",
|
|
32
|
+
"test:integration": "node --test --test-force-exit --test-concurrency=1 tests/integration/**/*.test.mjs",
|
|
33
|
+
"test:route-smoke": "node --test scripts/test-tfx-route-no-claude-native.mjs"
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18.0.0"
|
|
37
|
+
},
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "git+https://github.com/tellang/triflux.git"
|
|
41
|
+
},
|
|
42
|
+
"homepage": "https://github.com/tellang/triflux#readme",
|
|
43
|
+
"author": "tellang",
|
|
44
|
+
"license": "MIT",
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
47
|
+
"better-sqlite3": "^12.6.2",
|
|
48
|
+
"systray2": "^2.1.4"
|
|
49
|
+
},
|
|
50
|
+
"keywords": [
|
|
51
|
+
"claude-code",
|
|
52
|
+
"plugin",
|
|
53
|
+
"codex",
|
|
54
|
+
"gemini",
|
|
55
|
+
"cli-routing",
|
|
56
|
+
"orchestration",
|
|
57
|
+
"multi-model",
|
|
58
|
+
"triflux",
|
|
59
|
+
"tfx"
|
|
60
|
+
]
|
|
61
|
+
}
|