triflux 10.13.7 → 10.13.8

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.
@@ -13,6 +13,54 @@ const RED = "\u001b[91m";
13
13
  const YELLOW = "\u001b[93m";
14
14
  const GRAY = "\u001b[90m";
15
15
 
16
+ /**
17
+ * #116-C: non-TTY background 환경에서 `tfx swarm` 실행은 codex worker spawn 이
18
+ * 무한 hang 한다 (stdin TTY 대기 또는 hub MCP lease race).
19
+ *
20
+ * - stdout/stdin 모두 non-TTY 이면 fail-fast — 명시 복구 경로 안내.
21
+ * - `TFX_ALLOW_NON_TTY_SWARM=1` opt-in 시 경고만 남기고 통과 (테스트/CI).
22
+ * - pure function — 테스트하기 쉽게 deps 주입 가능.
23
+ *
24
+ * @param {{
25
+ * stdoutIsTTY?: boolean,
26
+ * stdinIsTTY?: boolean,
27
+ * env?: Record<string,string|undefined>,
28
+ * }} [deps]
29
+ * @returns {{ ok: boolean, optIn: boolean, warnings: string[], reason?: string }}
30
+ */
31
+ export function assertTtyForSwarm(deps = {}) {
32
+ const stdoutIsTTY =
33
+ typeof deps.stdoutIsTTY === "boolean"
34
+ ? deps.stdoutIsTTY
35
+ : Boolean(process.stdout.isTTY);
36
+ const stdinIsTTY =
37
+ typeof deps.stdinIsTTY === "boolean"
38
+ ? deps.stdinIsTTY
39
+ : Boolean(process.stdin.isTTY);
40
+ const env = deps.env || process.env;
41
+ const warnings = [];
42
+
43
+ if (stdoutIsTTY || stdinIsTTY) {
44
+ return { ok: true, optIn: false, warnings };
45
+ }
46
+
47
+ if (env.TFX_ALLOW_NON_TTY_SWARM === "1") {
48
+ warnings.push(
49
+ "non-TTY 환경 감지 — TFX_ALLOW_NON_TTY_SWARM=1 opt-in 으로 진행합니다. codex worker spawn hang 가능성 존재 (#116-C).",
50
+ );
51
+ return { ok: true, optIn: true, warnings };
52
+ }
53
+
54
+ const reason =
55
+ "tfx swarm 은 TTY 가 필요합니다 — non-TTY 환경 (run_in_background, nohup 등) 에서 codex worker spawn 이 hang 합니다 (#116-C).\n" +
56
+ " 복구 경로:\n" +
57
+ " 1) 터미널에서 직접 실행: tfx swarm <prd>\n" +
58
+ " 2) tmux 경로: tfx multi --teammate-mode tmux --auto-attach --dashboard --assign ...\n" +
59
+ " 3) opt-in (위험): TFX_ALLOW_NON_TTY_SWARM=1 tfx swarm <prd>";
60
+
61
+ return { ok: false, optIn: false, warnings, reason };
62
+ }
63
+
16
64
  export function parseFlags(args) {
17
65
  const flags = {
18
66
  dryRun: false,
@@ -121,6 +169,14 @@ export async function cmdSwarmRun(args, { json = false } = {}) {
121
169
  return;
122
170
  }
123
171
 
172
+ const ttyGate = assertTtyForSwarm();
173
+ for (const w of ttyGate.warnings) {
174
+ console.error(` ${YELLOW}⚠${RESET} ${w}`);
175
+ }
176
+ if (!ttyGate.ok) {
177
+ throw new Error(ttyGate.reason);
178
+ }
179
+
124
180
  const logsDir =
125
181
  flags.logsDir ||
126
182
  join(process.cwd(), ".triflux", "swarm-logs", `run-${Date.now()}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "triflux",
3
- "version": "10.13.7",
3
+ "version": "10.13.8",
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": {