triflux 4.0.1 → 4.0.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/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;
@@ -22,7 +22,7 @@ const GIT_BASH_CANDIDATES = [
22
22
  function findGitBashExe() {
23
23
  for (const p of GIT_BASH_CANDIDATES) {
24
24
  try {
25
- execSync(`"${p}" --version`, { stdio: "ignore", timeout: 3000 });
25
+ execSync(`"${p}" --version`, { stdio: "ignore", timeout: 3000, windowsHide: true });
26
26
  return p;
27
27
  } catch {
28
28
  // 다음 후보
@@ -35,7 +35,7 @@ function findGitBashExe() {
35
35
  export function hasWindowsTerminal() {
36
36
  if (process.platform !== "win32") return false;
37
37
  try {
38
- execSync("where wt.exe", { stdio: "ignore", timeout: 3000 });
38
+ execSync("where wt.exe", { stdio: "ignore", timeout: 3000, windowsHide: true });
39
39
  return true;
40
40
  } catch {
41
41
  return false;
@@ -50,7 +50,7 @@ export function hasWindowsTerminalSession() {
50
50
  /** tmux 실행 가능 여부 확인 */
51
51
  function hasTmux() {
52
52
  try {
53
- execSync("tmux -V", { stdio: "ignore", timeout: 3000 });
53
+ execSync("tmux -V", { stdio: "ignore", timeout: 3000, windowsHide: true });
54
54
  return true;
55
55
  } catch {
56
56
  return false;
@@ -60,7 +60,7 @@ function hasTmux() {
60
60
  /** WSL2 내 tmux 사용 가능 여부 (Windows 전용) */
61
61
  function hasWslTmux() {
62
62
  try {
63
- execSync("wsl tmux -V", { stdio: "ignore", timeout: 5000 });
63
+ execSync("wsl tmux -V", { stdio: "ignore", timeout: 5000, windowsHide: true });
64
64
  return true;
65
65
  } catch {
66
66
  return false;
@@ -76,6 +76,7 @@ function hasGitBashTmux() {
76
76
  encoding: "utf8",
77
77
  timeout: 5000,
78
78
  stdio: ["ignore", "pipe", "pipe"],
79
+ windowsHide: true,
79
80
  });
80
81
  return (r.status ?? 1) === 0;
81
82
  } catch {
@@ -130,6 +131,7 @@ function tmux(args, opts = {}) {
130
131
  encoding: "utf8",
131
132
  timeout: 10000,
132
133
  stdio: ["pipe", "pipe", "pipe"],
134
+ windowsHide: true,
133
135
  ...opts,
134
136
  });
135
137
  if ((r.status ?? 1) !== 0) {
@@ -145,6 +147,7 @@ function tmux(args, opts = {}) {
145
147
  encoding: "utf8",
146
148
  timeout: 10000,
147
149
  stdio: ["pipe", "pipe", "pipe"],
150
+ windowsHide: true,
148
151
  ...opts,
149
152
  });
150
153
  return result != null ? result.trim() : "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "triflux",
3
- "version": "4.0.1",
3
+ "version": "4.0.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": {
@@ -12,7 +12,7 @@ const CACHE_TTL_MS = 30_000; // 30초
12
12
 
13
13
  function checkHub() {
14
14
  try {
15
- const res = execSync("curl -sf http://127.0.0.1:27888/status", { timeout: 3000, encoding: "utf8" });
15
+ const res = execSync("curl -sf http://127.0.0.1:27888/status", { timeout: 3000, encoding: "utf8", windowsHide: true });
16
16
  const data = JSON.parse(res);
17
17
  return { ok: true, state: data?.hub?.state || "unknown", pid: data?.pid };
18
18
  } catch {
@@ -27,7 +27,7 @@ function checkRoute() {
27
27
 
28
28
  function checkCli(name) {
29
29
  try {
30
- const path = execSync(`which ${name} 2>/dev/null || where ${name} 2>nul`, { encoding: "utf8", timeout: 2000 }).trim();
30
+ const path = execSync(`which ${name} 2>/dev/null || where ${name} 2>nul`, { encoding: "utf8", timeout: 2000, windowsHide: true }).trim();
31
31
  return { ok: !!path, path };
32
32
  } catch {
33
33
  return { ok: false };