triflux 10.3.4 → 10.7.0

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.
Files changed (329) hide show
  1. package/LICENSE +21 -21
  2. package/bin/tfx-doctor-tui.mjs +1 -1
  3. package/bin/tfx-doctor.mjs +6 -1
  4. package/bin/tfx-profile.mjs +1 -1
  5. package/bin/tfx-setup-tui.mjs +1 -1
  6. package/bin/tfx-setup.mjs +6 -1
  7. package/bin/triflux.mjs +2396 -1140
  8. package/hooks/agent-route-guard.mjs +12 -8
  9. package/hooks/cross-review-tracker.mjs +21 -8
  10. package/hooks/error-context.mjs +19 -7
  11. package/hooks/hook-adaptive-collector.mjs +18 -16
  12. package/hooks/hook-manager.mjs +93 -32
  13. package/hooks/hook-orchestrator.mjs +108 -24
  14. package/hooks/hook-registry.json +11 -0
  15. package/hooks/keyword-rules.json +6 -10
  16. package/hooks/lib/resolve-root.mjs +1 -1
  17. package/hooks/mcp-config-watcher.mjs +6 -2
  18. package/hooks/pipeline-stop.mjs +3 -6
  19. package/hooks/safety-guard.mjs +99 -28
  20. package/hooks/session-start-fast.mjs +143 -0
  21. package/hooks/subagent-verifier.mjs +5 -4
  22. package/hub/account-broker.mjs +256 -60
  23. package/hub/adaptive-diagnostic.mjs +75 -48
  24. package/hub/adaptive-inject.mjs +95 -57
  25. package/hub/adaptive-memory.mjs +156 -42
  26. package/hub/adaptive.mjs +60 -31
  27. package/hub/assign-callbacks.mjs +67 -30
  28. package/hub/bridge.mjs +0 -1
  29. package/hub/cli-adapter-base.mjs +200 -48
  30. package/hub/codex-adapter.mjs +76 -96
  31. package/hub/codex-compat.mjs +3 -3
  32. package/hub/codex-preflight.mjs +63 -37
  33. package/hub/delegator/contracts.mjs +19 -23
  34. package/hub/delegator/index.mjs +3 -3
  35. package/hub/delegator/service.mjs +88 -64
  36. package/hub/delegator/tool-definitions.mjs +5 -5
  37. package/hub/fullcycle.mjs +33 -17
  38. package/hub/gemini-adapter.mjs +69 -94
  39. package/hub/hitl.mjs +89 -30
  40. package/hub/intent.mjs +161 -38
  41. package/hub/lib/cache-guard.mjs +43 -17
  42. package/hub/lib/mcp-response-cache.mjs +66 -32
  43. package/hub/lib/memory-store.mjs +285 -111
  44. package/hub/lib/path-utils.mjs +35 -37
  45. package/hub/lib/process-utils.mjs +106 -37
  46. package/hub/lib/spawn-trace.mjs +527 -0
  47. package/hub/lib/ssh-command.mjs +34 -4
  48. package/hub/lib/ssh-retry.mjs +5 -1
  49. package/hub/lib/uuidv7.mjs +4 -3
  50. package/hub/memory-doctor.mjs +266 -106
  51. package/hub/middleware/request-logger.mjs +61 -34
  52. package/hub/paths.mjs +9 -9
  53. package/hub/pipeline/gates/confidence.mjs +34 -15
  54. package/hub/pipeline/gates/consensus.mjs +27 -15
  55. package/hub/pipeline/gates/index.mjs +7 -3
  56. package/hub/pipeline/gates/selfcheck.mjs +57 -19
  57. package/hub/pipeline/index.mjs +77 -42
  58. package/hub/pipeline/state.mjs +10 -10
  59. package/hub/pipeline/transitions.mjs +40 -23
  60. package/hub/platform.mjs +57 -48
  61. package/hub/promote-penalties.mjs +25 -7
  62. package/hub/quality/deslop.mjs +70 -49
  63. package/hub/research.mjs +32 -25
  64. package/hub/router.mjs +240 -107
  65. package/hub/routing/complexity.mjs +132 -29
  66. package/hub/routing/index.mjs +17 -12
  67. package/hub/routing/q-learning.mjs +76 -28
  68. package/hub/server.mjs +4 -4
  69. package/hub/session-fingerprint.mjs +126 -60
  70. package/hub/state.mjs +84 -43
  71. package/hub/store-adapter.mjs +59 -26
  72. package/hub/store.mjs +356 -153
  73. package/hub/team/agent-map.json +22 -7
  74. package/hub/team/ansi.mjs +186 -122
  75. package/hub/team/backend.mjs +28 -10
  76. package/hub/team/cli/commands/attach.mjs +29 -9
  77. package/hub/team/cli/commands/control.mjs +29 -8
  78. package/hub/team/cli/commands/debug.mjs +32 -11
  79. package/hub/team/cli/commands/focus.mjs +38 -11
  80. package/hub/team/cli/commands/interrupt.mjs +18 -6
  81. package/hub/team/cli/commands/kill.mjs +16 -5
  82. package/hub/team/cli/commands/list.mjs +11 -4
  83. package/hub/team/cli/commands/send.mjs +19 -6
  84. package/hub/team/cli/commands/start/index.mjs +154 -31
  85. package/hub/team/cli/commands/start/parse-args.mjs +38 -11
  86. package/hub/team/cli/commands/start/start-headless.mjs +112 -36
  87. package/hub/team/cli/commands/start/start-in-process.mjs +12 -2
  88. package/hub/team/cli/commands/start/start-mux.mjs +70 -21
  89. package/hub/team/cli/commands/start/start-wt.mjs +29 -12
  90. package/hub/team/cli/commands/status.mjs +43 -14
  91. package/hub/team/cli/commands/stop.mjs +11 -4
  92. package/hub/team/cli/commands/task.mjs +8 -3
  93. package/hub/team/cli/commands/tasks.mjs +1 -1
  94. package/hub/team/cli/index.mjs +2 -2
  95. package/hub/team/cli/manifest.mjs +38 -8
  96. package/hub/team/cli/render.mjs +30 -8
  97. package/hub/team/cli/services/attach-fallback.mjs +31 -11
  98. package/hub/team/cli/services/hub-client.mjs +42 -14
  99. package/hub/team/cli/services/member-selector.mjs +11 -4
  100. package/hub/team/cli/services/native-control.mjs +48 -21
  101. package/hub/team/cli/services/runtime-mode.mjs +2 -1
  102. package/hub/team/cli/services/state-store.mjs +25 -8
  103. package/hub/team/cli/services/task-model.mjs +16 -6
  104. package/hub/team/conductor-mesh-bridge.mjs +24 -23
  105. package/hub/team/conductor.mjs +8 -4
  106. package/hub/team/dashboard-anchor.mjs +4 -5
  107. package/hub/team/dashboard-layout.mjs +3 -1
  108. package/hub/team/dashboard-open.mjs +41 -21
  109. package/hub/team/dashboard.mjs +76 -28
  110. package/hub/team/event-log.mjs +18 -10
  111. package/hub/team/handoff.mjs +31 -15
  112. package/hub/team/headless.mjs +2 -1
  113. package/hub/team/health-probe.mjs +69 -54
  114. package/hub/team/launcher-template.mjs +16 -13
  115. package/hub/team/native-supervisor.mjs +65 -21
  116. package/hub/team/native.mjs +74 -35
  117. package/hub/team/nativeProxy.mjs +184 -113
  118. package/hub/team/notify.mjs +119 -76
  119. package/hub/team/orchestrator.mjs +9 -4
  120. package/hub/team/pane.mjs +12 -7
  121. package/hub/team/process-cleanup.mjs +25 -16
  122. package/hub/team/psmux.mjs +491 -201
  123. package/hub/team/remote-probe.mjs +68 -52
  124. package/hub/team/remote-session.mjs +117 -59
  125. package/hub/team/remote-watcher.mjs +61 -33
  126. package/hub/team/routing.mjs +51 -25
  127. package/hub/team/runtime-strategy.mjs +3 -1
  128. package/hub/team/session.mjs +98 -34
  129. package/hub/team/staleState.mjs +72 -30
  130. package/hub/team/swarm-locks.mjs +15 -13
  131. package/hub/team/swarm-planner.mjs +32 -21
  132. package/hub/team/swarm-reconciler.mjs +48 -23
  133. package/hub/team/tui-lite.mjs +266 -68
  134. package/hub/team/tui-remote-adapter.mjs +14 -10
  135. package/hub/team/tui-viewer.mjs +99 -43
  136. package/hub/team/tui.mjs +708 -271
  137. package/hub/team/worktree-lifecycle.mjs +152 -58
  138. package/hub/team/wt-manager.mjs +24 -14
  139. package/hub/token-mode.mjs +71 -71
  140. package/hub/tray.mjs +66 -23
  141. package/hub/workers/claude-worker.mjs +162 -118
  142. package/hub/workers/codex-mcp.mjs +192 -141
  143. package/hub/workers/delegator-mcp.mjs +507 -333
  144. package/hub/workers/factory.mjs +8 -8
  145. package/hub/workers/gemini-worker.mjs +115 -84
  146. package/hub/workers/interface.mjs +6 -1
  147. package/hub/workers/worker-utils.mjs +21 -14
  148. package/hud/colors.mjs +27 -9
  149. package/hud/constants.mjs +162 -26
  150. package/hud/context-monitor.mjs +82 -41
  151. package/hud/hud-qos-status.mjs +129 -49
  152. package/hud/mission-board.mjs +6 -3
  153. package/hud/providers/claude.mjs +226 -115
  154. package/hud/providers/codex.mjs +62 -22
  155. package/hud/providers/gemini.mjs +168 -56
  156. package/hud/renderers.mjs +384 -119
  157. package/hud/terminal.mjs +101 -31
  158. package/hud/utils.mjs +78 -38
  159. package/mesh/index.mjs +11 -5
  160. package/mesh/mesh-budget.mjs +18 -9
  161. package/mesh/mesh-heartbeat.mjs +1 -1
  162. package/mesh/mesh-queue.mjs +3 -5
  163. package/mesh/mesh-router.mjs +5 -4
  164. package/package.json +2 -1
  165. package/scripts/__tests__/gen-skill-docs.test.mjs +36 -7
  166. package/scripts/__tests__/keyword-detector.test.mjs +77 -28
  167. package/scripts/__tests__/mcp-guard-engine.test.mjs +58 -20
  168. package/scripts/__tests__/remote-spawn-transfer.test.mjs +30 -19
  169. package/scripts/__tests__/remote-spawn.test.mjs +10 -4
  170. package/scripts/__tests__/session-start-fast.test.mjs +36 -0
  171. package/scripts/__tests__/skill-template.test.mjs +98 -50
  172. package/scripts/__tests__/smoke.test.mjs +1 -1
  173. package/scripts/__tests__/spawn-trace.test.mjs +102 -0
  174. package/scripts/__tests__/tfx-doctor-diagnose.test.mjs +48 -0
  175. package/scripts/cache-doctor.mjs +11 -4
  176. package/scripts/cache-warmup.mjs +96 -37
  177. package/scripts/claudemd-sync.mjs +27 -17
  178. package/scripts/codex-gateway-preflight.mjs +52 -37
  179. package/scripts/codex-mcp-gateway-sync.mjs +59 -39
  180. package/scripts/completions/tfx.bash +47 -47
  181. package/scripts/completions/tfx.fish +44 -44
  182. package/scripts/completions/tfx.zsh +83 -83
  183. package/scripts/config-audit.mjs +232 -0
  184. package/scripts/convert-to-tmpl.mjs +54 -0
  185. package/scripts/cross-review-gate.mjs +35 -12
  186. package/scripts/cross-review-tracker.mjs +21 -8
  187. package/scripts/demo.mjs +35 -17
  188. package/scripts/doctor-diagnose.mjs +284 -0
  189. package/scripts/gen-skill-docs.mjs +7 -2
  190. package/scripts/gen-skill-manifest.mjs +2 -1
  191. package/scripts/headless-guard.mjs +86 -48
  192. package/scripts/hub-ensure.mjs +45 -26
  193. package/scripts/keyword-detector.mjs +41 -20
  194. package/scripts/keyword-rules-expander.mjs +47 -30
  195. package/scripts/lib/claudemd-scanner.mjs +6 -1
  196. package/scripts/lib/context.mjs +3 -3
  197. package/scripts/lib/cross-review-utils.mjs +6 -3
  198. package/scripts/lib/env-probe.mjs +47 -28
  199. package/scripts/lib/gemini-profiles.mjs +44 -10
  200. package/scripts/lib/handoff.mjs +33 -17
  201. package/scripts/lib/hook-utils.mjs +8 -6
  202. package/scripts/lib/keyword-rules.mjs +43 -19
  203. package/scripts/lib/logger.mjs +24 -24
  204. package/scripts/lib/mcp-filter.mjs +377 -239
  205. package/scripts/lib/mcp-guard-engine.mjs +194 -79
  206. package/scripts/lib/mcp-manifest.mjs +23 -13
  207. package/scripts/lib/mcp-server-catalog.mjs +300 -63
  208. package/scripts/lib/psmux-info.mjs +11 -6
  209. package/scripts/lib/remote-spawn-transfer.mjs +44 -14
  210. package/scripts/lib/skill-template.mjs +30 -7
  211. package/scripts/mcp-check.mjs +58 -39
  212. package/scripts/mcp-gateway-config.mjs +83 -39
  213. package/scripts/mcp-gateway-ensure.mjs +43 -35
  214. package/scripts/mcp-gateway-integration-test.mjs +70 -58
  215. package/scripts/mcp-gateway-start.mjs +126 -60
  216. package/scripts/mcp-gateway-verify.mjs +24 -22
  217. package/scripts/mcp-safety-guard.mjs +44 -11
  218. package/scripts/notion-read.mjs +199 -84
  219. package/scripts/pack.mjs +94 -89
  220. package/scripts/preflight-cache.mjs +27 -10
  221. package/scripts/preinstall.mjs +42 -13
  222. package/scripts/remote-spawn.mjs +309 -94
  223. package/scripts/run.cjs +8 -5
  224. package/scripts/session-spawn-helper.mjs +130 -39
  225. package/scripts/session-stale-cleanup.mjs +123 -0
  226. package/scripts/setup.mjs +941 -492
  227. package/scripts/test-lock.mjs +20 -7
  228. package/scripts/test-tfx-route-no-claude-native.mjs +16 -12
  229. package/scripts/tfx-batch-stats.mjs +32 -11
  230. package/scripts/tfx-gate-activate.mjs +11 -4
  231. package/scripts/tfx-route-post.mjs +87 -20
  232. package/scripts/tfx-route-worker.mjs +57 -51
  233. package/scripts/tfx-route.sh +41 -124
  234. package/scripts/tmp-cleanup.mjs +21 -7
  235. package/scripts/token-snapshot.mjs +204 -85
  236. package/skills/.omc/state/agent-replay-8f0e10a9-9693-4410-96f5-a6b07e8ed995.jsonl +1 -0
  237. package/skills/.omc/state/idle-notif-cooldown.json +3 -0
  238. package/skills/.omc/state/last-tool-error.json +7 -0
  239. package/skills/.omc/state/subagent-tracking.json +7 -0
  240. package/skills/_templates/base.md +1 -6
  241. package/skills/merge-worktree/SKILL.md.tmpl +144 -0
  242. package/skills/shared/telemetry-segment.md +6 -0
  243. package/skills/star-prompt/SKILL.md.tmpl +222 -0
  244. package/skills/tfx-analysis/SKILL.md.tmpl +107 -0
  245. package/skills/tfx-analysis/skill.json +1 -6
  246. package/skills/tfx-auto/SKILL.md +1 -0
  247. package/skills/tfx-auto-codex/SKILL.md.tmpl +106 -0
  248. package/skills/tfx-auto-codex/skill.json +1 -3
  249. package/skills/tfx-autopilot/SKILL.md.tmpl +116 -0
  250. package/skills/tfx-autopilot/skill.json +1 -5
  251. package/skills/tfx-autoresearch/SKILL.md.tmpl +136 -0
  252. package/skills/tfx-autoroute/SKILL.md.tmpl +189 -0
  253. package/skills/tfx-autoroute/skill.json +1 -7
  254. package/skills/tfx-codex/SKILL.md +1 -0
  255. package/skills/tfx-codex/skill.json +1 -3
  256. package/skills/tfx-codex-swarm/SKILL.md.tmpl +16 -0
  257. package/skills/tfx-codex-swarm/evals/evals.json +1 -1
  258. package/skills/tfx-codex-swarm/skill.json +1 -4
  259. package/skills/tfx-codex-swarm-workspace/iteration-1/benchmark.json +54 -12
  260. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/grading.json +35 -7
  261. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/grading.json +35 -7
  262. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/grading.json +25 -5
  263. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/grading.json +25 -5
  264. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/grading.json +20 -4
  265. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/grading.json +16 -4
  266. package/skills/tfx-consensus/SKILL.md.tmpl +146 -0
  267. package/skills/tfx-debate/SKILL.md.tmpl +192 -0
  268. package/skills/tfx-debate/skill.json +1 -7
  269. package/skills/tfx-deep-analysis/SKILL.md.tmpl +228 -0
  270. package/skills/tfx-deep-analysis/skill.json +1 -5
  271. package/skills/tfx-deep-interview/SKILL.md.tmpl +203 -0
  272. package/skills/tfx-deep-plan/SKILL.md.tmpl +282 -0
  273. package/skills/tfx-deep-qa/SKILL.md.tmpl +165 -0
  274. package/skills/tfx-deep-qa/skill.json +1 -6
  275. package/skills/tfx-deep-research/SKILL.md.tmpl +217 -0
  276. package/skills/tfx-deep-review/SKILL.md.tmpl +179 -0
  277. package/skills/tfx-doctor/SKILL.md +21 -0
  278. package/skills/tfx-doctor/SKILL.md.tmpl +172 -0
  279. package/skills/tfx-doctor/skill.json +1 -3
  280. package/skills/tfx-find/SKILL.md +1 -0
  281. package/skills/tfx-forge/SKILL.md.tmpl +187 -0
  282. package/skills/tfx-fullcycle/SKILL.md.tmpl +286 -0
  283. package/skills/tfx-fullcycle/skill.json +1 -6
  284. package/skills/tfx-gemini/SKILL.md.tmpl +91 -0
  285. package/skills/tfx-gemini/skill.json +1 -3
  286. package/skills/tfx-hooks/SKILL.md.tmpl +216 -0
  287. package/skills/tfx-hooks/skill.json +1 -3
  288. package/skills/tfx-hub/SKILL.md.tmpl +212 -0
  289. package/skills/tfx-hub/skill.json +1 -3
  290. package/skills/tfx-index/SKILL.md +1 -0
  291. package/skills/tfx-index/skill.json +1 -6
  292. package/skills/tfx-interview/SKILL.md.tmpl +285 -0
  293. package/skills/tfx-multi/SKILL.md.tmpl +183 -0
  294. package/skills/tfx-multi/skill.json +1 -3
  295. package/skills/tfx-panel/SKILL.md.tmpl +189 -0
  296. package/skills/tfx-panel/skill.json +1 -7
  297. package/skills/tfx-persist/SKILL.md.tmpl +270 -0
  298. package/skills/tfx-persist/skill.json +1 -7
  299. package/skills/tfx-plan/SKILL.md +1 -0
  300. package/skills/tfx-plan/skill.json +1 -6
  301. package/skills/tfx-profile/SKILL.md.tmpl +239 -0
  302. package/skills/tfx-profile/skill.json +1 -3
  303. package/skills/tfx-prune/SKILL.md.tmpl +200 -0
  304. package/skills/tfx-prune/skill.json +1 -7
  305. package/skills/tfx-psmux-rules/SKILL.md.tmpl +326 -0
  306. package/skills/tfx-psmux-rules/skill.json +1 -4
  307. package/skills/tfx-qa/SKILL.md +1 -0
  308. package/skills/tfx-qa/skill.json +1 -6
  309. package/skills/tfx-ralph/SKILL.md.tmpl +28 -0
  310. package/skills/tfx-ralph/skill.json +1 -4
  311. package/skills/tfx-remote-setup/SKILL.md.tmpl +576 -0
  312. package/skills/tfx-remote-setup/skill.json +1 -3
  313. package/skills/tfx-remote-spawn/SKILL.md.tmpl +263 -0
  314. package/skills/tfx-remote-spawn/references/hosts.json +16 -0
  315. package/skills/tfx-remote-spawn/skill.json +1 -4
  316. package/skills/tfx-research/SKILL.md +1 -0
  317. package/skills/tfx-review/SKILL.md +1 -0
  318. package/skills/tfx-review/skill.json +1 -6
  319. package/skills/tfx-setup/SKILL.md.tmpl +504 -0
  320. package/skills/tfx-setup/skill.json +1 -3
  321. package/skills/tfx-swarm/SKILL.md +22 -0
  322. package/skills/tfx-swarm/SKILL.md.tmpl +218 -0
  323. package/tui/codex-profile.mjs +88 -33
  324. package/tui/core.mjs +45 -15
  325. package/tui/doctor.mjs +75 -28
  326. package/tui/gemini-profile.mjs +74 -29
  327. package/tui/monitor-data.mjs +8 -4
  328. package/tui/monitor.mjs +71 -27
  329. package/tui/setup.mjs +133 -42
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  // tfx-route-worker.mjs — tfx-route.sh용 subprocess worker 러너
3
3
 
4
- import { readFileSync, existsSync } from 'node:fs';
5
- import { dirname, resolve } from 'node:path';
6
- import { fileURLToPath, pathToFileURL } from 'node:url';
4
+ import { existsSync, readFileSync } from "node:fs";
5
+ import { dirname, resolve } from "node:path";
6
+ import { fileURLToPath, pathToFileURL } from "node:url";
7
7
 
8
8
  const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
9
9
  const FACTORY_CANDIDATES = [
10
- resolve(SCRIPT_DIR, '../hub/workers/factory.mjs'),
11
- resolve(SCRIPT_DIR, './hub/workers/factory.mjs'),
10
+ resolve(SCRIPT_DIR, "../hub/workers/factory.mjs"),
11
+ resolve(SCRIPT_DIR, "./hub/workers/factory.mjs"),
12
12
  ];
13
13
 
14
14
  // MCP transport 실패 시 tfx-route.sh가 exec fallback을 수행할 수 있도록
@@ -16,10 +16,10 @@ const FACTORY_CANDIDATES = [
16
16
  const MCP_TRANSPORT_EXIT_CODE = 70;
17
17
  const GEMINI_RETRY_DELAY_MS = 5000;
18
18
  const GEMINI_RETRY_PATTERN_SNIPPETS = [
19
- '429',
20
- 'quota',
21
- 'rate limit',
22
- 'resource_exhausted',
19
+ "429",
20
+ "quota",
21
+ "rate limit",
22
+ "resource_exhausted",
23
23
  ];
24
24
 
25
25
  let createWorker = null;
@@ -30,8 +30,10 @@ for (const candidate of FACTORY_CANDIDATES) {
30
30
  ({ createWorker } = await import(pathToFileURL(candidate).href));
31
31
  } catch (err) {
32
32
  // 의존성 누락 (예: @modelcontextprotocol/sdk) → fallback 가능하도록 exit 70
33
- if (err.code === 'ERR_MODULE_NOT_FOUND') {
34
- process.stderr.write(`[tfx-route-worker] 모듈 로드 실패: ${err.message}\n`);
33
+ if (err.code === "ERR_MODULE_NOT_FOUND") {
34
+ process.stderr.write(
35
+ `[tfx-route-worker] 모듈 로드 실패: ${err.message}\n`,
36
+ );
35
37
  process.exit(MCP_TRANSPORT_EXIT_CODE);
36
38
  }
37
39
  throw err;
@@ -40,7 +42,9 @@ for (const candidate of FACTORY_CANDIDATES) {
40
42
  }
41
43
 
42
44
  if (!createWorker) {
43
- process.stderr.write('[tfx-route-worker] worker factory를 찾지 못했습니다.\n');
45
+ process.stderr.write(
46
+ "[tfx-route-worker] worker factory를 찾지 못했습니다.\n",
47
+ );
44
48
  process.exit(MCP_TRANSPORT_EXIT_CODE);
45
49
  }
46
50
 
@@ -55,46 +59,46 @@ function parseArgs(argv) {
55
59
  const next = argv[index + 1];
56
60
 
57
61
  switch (token) {
58
- case '--type':
62
+ case "--type":
59
63
  args.type = next;
60
64
  index += 1;
61
65
  break;
62
- case '--command':
66
+ case "--command":
63
67
  args.command = next;
64
68
  index += 1;
65
69
  break;
66
- case '--command-args-json':
70
+ case "--command-args-json":
67
71
  args.commandArgsJson = next;
68
72
  index += 1;
69
73
  break;
70
- case '--model':
74
+ case "--model":
71
75
  args.model = next;
72
76
  index += 1;
73
77
  break;
74
- case '--timeout-ms':
78
+ case "--timeout-ms":
75
79
  args.timeoutMs = Number(next);
76
80
  index += 1;
77
81
  break;
78
- case '--approval-mode':
82
+ case "--approval-mode":
79
83
  args.approvalMode = next;
80
84
  index += 1;
81
85
  break;
82
- case '--permission-mode':
86
+ case "--permission-mode":
83
87
  args.permissionMode = next;
84
88
  index += 1;
85
89
  break;
86
- case '--allow-dangerously-skip-permissions':
90
+ case "--allow-dangerously-skip-permissions":
87
91
  args.allowDangerouslySkipPermissions = true;
88
92
  break;
89
- case '--allowed-mcp-server-name':
93
+ case "--allowed-mcp-server-name":
90
94
  args.allowedMcpServerNames.push(next);
91
95
  index += 1;
92
96
  break;
93
- case '--mcp-config':
97
+ case "--mcp-config":
94
98
  args.mcpConfig.push(next);
95
99
  index += 1;
96
100
  break;
97
- case '--cwd':
101
+ case "--cwd":
98
102
  args.cwd = next;
99
103
  index += 1;
100
104
  break;
@@ -104,7 +108,7 @@ function parseArgs(argv) {
104
108
  }
105
109
 
106
110
  if (!args.type) {
107
- throw new Error('--type is required');
111
+ throw new Error("--type is required");
108
112
  }
109
113
 
110
114
  return args;
@@ -124,15 +128,17 @@ function parseJsonArray(raw, label) {
124
128
  }
125
129
 
126
130
  function readPromptFromStdin() {
127
- return readFileSync(0, 'utf8');
131
+ return readFileSync(0, "utf8");
128
132
  }
129
133
 
130
134
  function resolveDefaultMcpConfig(cwd) {
131
- const primary = resolve(cwd, '.claude', 'mcp.json');
135
+ const primary = resolve(cwd, ".claude", "mcp.json");
132
136
  if (existsSync(primary)) return [primary];
133
- const legacy = resolve(cwd, '.mcp.json');
137
+ const legacy = resolve(cwd, ".mcp.json");
134
138
  if (existsSync(legacy)) return [legacy];
135
- process.stderr.write('[tfx-route-worker] warning: no MCP config found, hub unavailable\n');
139
+ process.stderr.write(
140
+ "[tfx-route-worker] warning: no MCP config found, hub unavailable\n",
141
+ );
136
142
  return [];
137
143
  }
138
144
 
@@ -145,21 +151,19 @@ function isGeminiQuotaRetrySignal(error) {
145
151
  return true;
146
152
  }
147
153
 
148
- const fragments = [
149
- error?.message,
150
- error?.stderr,
151
- error?.result?.stderr,
152
- ]
153
- .filter((value) => typeof value === 'string' && value.trim().length > 0)
154
+ const fragments = [error?.message, error?.stderr, error?.result?.stderr]
155
+ .filter((value) => typeof value === "string" && value.trim().length > 0)
154
156
  .map((value) => value.toLowerCase());
155
157
 
156
158
  if (fragments.length === 0) return false;
157
- const merged = fragments.join('\n');
158
- return GEMINI_RETRY_PATTERN_SNIPPETS.some((pattern) => merged.includes(pattern));
159
+ const merged = fragments.join("\n");
160
+ return GEMINI_RETRY_PATTERN_SNIPPETS.some((pattern) =>
161
+ merged.includes(pattern),
162
+ );
159
163
  }
160
164
 
161
165
  async function runWorker(worker, type, prompt) {
162
- const maxAttempts = type === 'gemini' ? 2 : 1;
166
+ const maxAttempts = type === "gemini" ? 2 : 1;
163
167
  let lastError = null;
164
168
 
165
169
  for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
@@ -167,18 +171,17 @@ async function runWorker(worker, type, prompt) {
167
171
  return await worker.run(prompt);
168
172
  } catch (error) {
169
173
  lastError = error;
170
- const shouldRetry = (
171
- type === 'gemini'
172
- && attempt < maxAttempts
173
- && isGeminiQuotaRetrySignal(error)
174
- );
174
+ const shouldRetry =
175
+ type === "gemini" &&
176
+ attempt < maxAttempts &&
177
+ isGeminiQuotaRetrySignal(error);
175
178
 
176
179
  if (!shouldRetry) {
177
180
  throw error;
178
181
  }
179
182
 
180
183
  process.stderr.write(
181
- '[tfx-route-worker] Gemini 429/quota 감지 — 5초 후 1회 재시도합니다.\n',
184
+ "[tfx-route-worker] Gemini 429/quota 감지 — 5초 후 1회 재시도합니다.\n",
182
185
  );
183
186
  await sleep(GEMINI_RETRY_DELAY_MS);
184
187
  }
@@ -192,16 +195,17 @@ const prompt = readPromptFromStdin();
192
195
 
193
196
  const worker = createWorker(args.type, {
194
197
  command: args.command,
195
- commandArgs: parseJsonArray(args.commandArgsJson, '--command-args-json'),
198
+ commandArgs: parseJsonArray(args.commandArgsJson, "--command-args-json"),
196
199
  model: args.model,
197
200
  timeoutMs: args.timeoutMs,
198
201
  approvalMode: args.approvalMode,
199
202
  permissionMode: args.permissionMode,
200
203
  allowDangerouslySkipPermissions: args.allowDangerouslySkipPermissions,
201
204
  allowedMcpServerNames: args.allowedMcpServerNames,
202
- mcpConfig: args.type === 'claude' && args.mcpConfig.length === 0
203
- ? resolveDefaultMcpConfig(args.cwd || process.cwd())
204
- : args.mcpConfig,
205
+ mcpConfig:
206
+ args.type === "claude" && args.mcpConfig.length === 0
207
+ ? resolveDefaultMcpConfig(args.cwd || process.cwd())
208
+ : args.mcpConfig,
205
209
  cwd: args.cwd || process.cwd(),
206
210
  });
207
211
 
@@ -209,15 +213,17 @@ try {
209
213
  const result = await runWorker(worker, args.type, prompt);
210
214
  if (result.response) {
211
215
  process.stdout.write(result.response);
212
- if (!result.response.endsWith('\n')) process.stdout.write('\n');
216
+ if (!result.response.endsWith("\n")) process.stdout.write("\n");
213
217
  }
214
218
  } catch (error) {
215
219
  if (error.stderr) {
216
220
  process.stderr.write(String(error.stderr));
217
- if (!String(error.stderr).endsWith('\n')) process.stderr.write('\n');
221
+ if (!String(error.stderr).endsWith("\n")) process.stderr.write("\n");
218
222
  }
219
223
  process.stderr.write(`${error.message}\n`);
220
- process.exitCode = error.code === 'ETIMEDOUT' ? 124 : 1;
224
+ process.exitCode = error.code === "ETIMEDOUT" ? 124 : 1;
221
225
  } finally {
222
- try { await worker.stop(); } catch {}
226
+ try {
227
+ await worker.stop();
228
+ } catch {}
223
229
  }
@@ -56,6 +56,37 @@ resolve_tmp_dir() {
56
56
 
57
57
  TFX_TMP="$(resolve_tmp_dir)"
58
58
 
59
+ # ── Worker PID 추적 (EXIT trap에서 정리) ──
60
+ _PID_TRACK="${TFX_TMP}/tfx-route-$$-pids"
61
+
62
+ track_worker_pid() {
63
+ echo "$1" >> "$_PID_TRACK"
64
+ }
65
+
66
+ cleanup_workers() {
67
+ deregister_agent 2>/dev/null || true
68
+ [[ ! -f "$_PID_TRACK" ]] && return
69
+ while IFS= read -r pid; do
70
+ [[ -z "$pid" ]] && continue
71
+ kill -0 "$pid" 2>/dev/null || continue
72
+ case "$(uname -s)" in
73
+ MINGW*|MSYS*)
74
+ # Windows: taskkill /T /F로 프로세스 트리 전체 종료
75
+ MSYS_NO_PATHCONV=1 cmd.exe //c "taskkill /T /F /PID $pid" 2>/dev/null || true ;;
76
+ *)
77
+ # Unix: 프로세스 그룹 kill
78
+ local pgid
79
+ pgid=$(ps -o pgid= -p "$pid" 2>/dev/null | tr -d ' ')
80
+ if [[ -n "$pgid" && "$pgid" != "0" ]]; then
81
+ kill -- "-$pgid" 2>/dev/null || true
82
+ else
83
+ kill "$pid" 2>/dev/null || true
84
+ fi ;;
85
+ esac
86
+ done < "$_PID_TRACK"
87
+ rm -f "$_PID_TRACK"
88
+ }
89
+
59
90
  # ── config.toml sandbox/approval_mode 감지 ──
60
91
  # config.toml에 이미 설정되어 있으면 CLI 플래그 중복 시 Codex가 에러를 던짐
61
92
  _CODEX_CONFIG="${HOME}/.codex/config.toml"
@@ -1375,6 +1406,7 @@ run_stream_worker() {
1375
1406
  printf '%s' "$prompt" | "$TIMEOUT_BIN" "$TIMEOUT_SEC" "${worker_cmd[@]}" >"$STDOUT_LOG" 2>"$STDERR_LOG" &
1376
1407
  fi
1377
1408
  worker_pid=$!
1409
+ track_worker_pid "$worker_pid"
1378
1410
 
1379
1411
  heartbeat_monitor "$worker_pid" &
1380
1412
  hb_pid=$!
@@ -1384,125 +1416,6 @@ run_stream_worker() {
1384
1416
  return "$exit_code_local"
1385
1417
  }
1386
1418
 
1387
- # Gemini 429 지수 백오프 재시도 래퍼
1388
- # 사용: gemini_with_retry <use_tee_flag> <gemini_args_array_name> <prompt>
1389
- # 429/rate limit 감지 시 최대 3회 재시도 (2→4→8초 백오프)
1390
- _gemini_run_once() {
1391
- local use_tee_flag="$1"
1392
- local prompt="$2"
1393
- shift 2
1394
- local -a g_args=("$@")
1395
-
1396
- if [[ "$use_tee_flag" == "true" ]]; then
1397
- "$TIMEOUT_BIN" "$TIMEOUT_SEC" "$CLI_CMD" "${g_args[@]}" "$prompt" 2>"$STDERR_LOG" | tee "$STDOUT_LOG" &
1398
- else
1399
- "$TIMEOUT_BIN" "$TIMEOUT_SEC" "$CLI_CMD" "${g_args[@]}" "$prompt" >"$STDOUT_LOG" 2>"$STDERR_LOG" &
1400
- fi
1401
- GEMINI_RUN_PID=$!
1402
- }
1403
-
1404
- gemini_with_retry() {
1405
- local use_tee_flag="$1"
1406
- local prompt="$2"
1407
- shift 2
1408
- local -a g_args=("$@")
1409
-
1410
- local max_retries=3
1411
- local attempt=0
1412
- local delay=2
1413
- local exit_code_local=0
1414
-
1415
- while (( attempt < max_retries )); do
1416
- exit_code_local=0
1417
- local pid
1418
- _gemini_run_once "$use_tee_flag" "$prompt" "${g_args[@]}"
1419
- pid="${GEMINI_RUN_PID:-}"
1420
- if [[ -z "$pid" ]]; then
1421
- echo "[tfx-route] Gemini: worker pid 획득 실패" >&2
1422
- return 1
1423
- fi
1424
-
1425
- local health_ok=true
1426
- local intervals=(1 2 3 5 8)
1427
- for wait_sec in "${intervals[@]}"; do
1428
- sleep "$wait_sec"
1429
- if [[ -s "$STDOUT_LOG" ]] || [[ -s "$STDERR_LOG" ]]; then
1430
- break
1431
- fi
1432
- if ! kill -0 "$pid" 2>/dev/null; then
1433
- health_ok=false
1434
- echo "[tfx-route] Gemini: 출력 없이 프로세스 종료 (${wait_sec}초 체크)" >&2
1435
- break
1436
- fi
1437
- done
1438
-
1439
- local hb_pid
1440
- if [[ "$health_ok" == "false" ]]; then
1441
- wait "$pid" 2>/dev/null
1442
- else
1443
- heartbeat_monitor "$pid" &
1444
- hb_pid=$!
1445
- wait "$pid" || exit_code_local=$?
1446
- kill "$hb_pid" 2>/dev/null; wait "$hb_pid" 2>/dev/null
1447
- fi
1448
-
1449
- # 성공 시 즉시 반환
1450
- if [[ $exit_code_local -eq 0 ]]; then
1451
- return 0
1452
- fi
1453
-
1454
- # 429 / rate limit 감지
1455
- if grep -qiE '429|rate.limit|too many requests' "$STDERR_LOG" 2>/dev/null; then
1456
- attempt=$(( attempt + 1 ))
1457
- if (( attempt < max_retries )); then
1458
- echo "[tfx-route] Gemini 429 감지. ${delay}초 후 재시도 ($attempt/$max_retries)..." >&2
1459
- kill "$pid" 2>/dev/null
1460
- wait "$pid" 2>/dev/null
1461
- sleep "$delay"
1462
- delay=$(( delay * 2 ))
1463
- : > "$STDOUT_LOG"
1464
- : > "$STDERR_LOG"
1465
- continue
1466
- else
1467
- echo "[tfx-route] Gemini 429: ${max_retries}회 재시도 실패" >&2
1468
- fi
1469
- fi
1470
-
1471
- # 비-429 에러 또는 최대 재시도 초과 시 즉시 반환
1472
- return "$exit_code_local"
1473
- done
1474
-
1475
- return "$exit_code_local"
1476
- }
1477
-
1478
- run_legacy_gemini() {
1479
- local prompt="$1"
1480
- local use_tee_flag="$2"
1481
- local -a gemini_args=()
1482
- read -r -a gemini_args <<< "$CLI_ARGS"
1483
-
1484
- if [[ ${#GEMINI_ALLOWED_SERVERS[@]} -gt 0 ]]; then
1485
- local gemini_mcp_filter prompt_index=-1
1486
- gemini_mcp_filter=$(IFS=,; echo "${GEMINI_ALLOWED_SERVERS[*]}")
1487
- for i in "${!gemini_args[@]}"; do
1488
- if [[ "${gemini_args[$i]}" == "--prompt" ]]; then
1489
- prompt_index="$i"
1490
- break
1491
- fi
1492
- done
1493
- if [[ "$prompt_index" -ge 0 ]]; then
1494
- gemini_args=(
1495
- "${gemini_args[@]:0:$prompt_index}"
1496
- "--allowed-mcp-server-names" "$gemini_mcp_filter"
1497
- "${gemini_args[@]:$prompt_index}"
1498
- )
1499
- echo "[tfx-route] Gemini MCP 필터: $gemini_mcp_filter" >&2
1500
- fi
1501
- fi
1502
-
1503
- gemini_with_retry "$use_tee_flag" "$prompt" "${gemini_args[@]}"
1504
- }
1505
-
1506
1419
  resolve_codex_mcp_script() {
1507
1420
  if [[ -n "${TFX_CODEX_MCP_SCRIPT:-}" && -f "$TFX_CODEX_MCP_SCRIPT" ]]; then
1508
1421
  printf '%s\n' "$TFX_CODEX_MCP_SCRIPT"
@@ -1547,6 +1460,7 @@ run_codex_exec() {
1547
1460
  "$TIMEOUT_BIN" "$TIMEOUT_SEC" "$CLI_CMD" "${codex_args[@]}" "$prompt" < /dev/null >"$STDOUT_LOG" 2>"$STDERR_LOG" &
1548
1461
  fi
1549
1462
  worker_pid=$!
1463
+ track_worker_pid "$worker_pid"
1550
1464
 
1551
1465
  heartbeat_monitor "$worker_pid" &
1552
1466
  hb_pid=$!
@@ -1641,6 +1555,7 @@ run_codex_mcp() {
1641
1555
  "$TIMEOUT_BIN" "$TIMEOUT_SEC" "$node_bin" "${mcp_args[@]}" >"$STDOUT_LOG" 2>"$STDERR_LOG" &
1642
1556
  fi
1643
1557
  worker_pid=$!
1558
+ track_worker_pid "$worker_pid"
1644
1559
 
1645
1560
  heartbeat_monitor "$worker_pid" &
1646
1561
  hb_pid=$!
@@ -1665,8 +1580,8 @@ run_codex_mcp() {
1665
1580
 
1666
1581
  # ── 메인 실행 ──
1667
1582
  main() {
1668
- # 종료 시 per-process 에이전트 파일 자동 삭제
1669
- trap 'deregister_agent' EXIT
1583
+ # 종료 시 per-process 에이전트 파일 + 워커 프로세스 정리
1584
+ trap 'cleanup_workers' EXIT
1670
1585
 
1671
1586
  route_agent "$AGENT_TYPE"
1672
1587
  apply_cli_mode
@@ -1847,11 +1762,13 @@ FALLBACK_EOF
1847
1762
 
1848
1763
  run_stream_worker "gemini" "$FULL_PROMPT" "$use_tee" "${gemini_worker_args[@]}" || exit_code=$?
1849
1764
  if [[ "$exit_code" -ne 0 && "$exit_code" -ne 124 ]]; then
1850
- echo "[tfx-route] Gemini stream wrapper 실패(exit=${exit_code}). legacy CLI 경로로 fallback합니다." >&2
1851
- : > "$STDOUT_LOG"
1765
+ echo "[tfx-route] Gemini stream wrapper 실패(exit=${exit_code}). claude-native fallback." >&2
1766
+ cat > "$STDOUT_LOG" <<EOF
1767
+ $(emit_claude_native_metadata)
1768
+ EOF
1852
1769
  : > "$STDERR_LOG"
1853
1770
  exit_code=0
1854
- run_legacy_gemini "$FULL_PROMPT" "$use_tee" || exit_code=$?
1771
+ CLI_TYPE="claude-native"
1855
1772
  fi
1856
1773
 
1857
1774
  elif [[ "$CLI_TYPE" == "claude" ]]; then
@@ -4,9 +4,9 @@
4
4
  * 7일 이상 된 파일을 삭제한다.
5
5
  * SessionStart 훅 또는 독립 실행으로 사용.
6
6
  */
7
- import { existsSync, readdirSync, statSync, rmSync } from "node:fs";
8
- import { join, resolve } from "node:path";
7
+ import { existsSync, readdirSync, rmSync, statSync } from "node:fs";
9
8
  import { tmpdir } from "node:os";
9
+ import { join, resolve } from "node:path";
10
10
 
11
11
  const MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000; // 7일
12
12
  const TRIFLUX_CLI_MAX_AGE_MS = 24 * 60 * 60 * 1000; // 1일
@@ -50,7 +50,11 @@ export async function cleanupTmpFiles({ protectPaths = [] } = {}) {
50
50
 
51
51
  // 1) tmpdir() 직하위의 관리 대상 항목 정리
52
52
  let topEntries;
53
- try { topEntries = readdirSync(tmp); } catch { topEntries = []; }
53
+ try {
54
+ topEntries = readdirSync(tmp);
55
+ } catch {
56
+ topEntries = [];
57
+ }
54
58
 
55
59
  for (const entry of topEntries) {
56
60
  const rule = TOP_LEVEL_RULES.find(({ prefix }) => entry.startsWith(prefix));
@@ -66,14 +70,20 @@ export async function cleanupTmpFiles({ protectPaths = [] } = {}) {
66
70
  rmSync(full, { recursive: true, force: true });
67
71
  cleaned++;
68
72
  }
69
- } catch { /* 권한 에러 등 무시 */ }
73
+ } catch {
74
+ /* 권한 에러 등 무시 */
75
+ }
70
76
  }
71
77
 
72
78
  // 2) tfx-headless/ 내 오래된 결과 파일 정리 (디렉터리 자체는 유지)
73
79
  const headlessDir = join(tmp, "tfx-headless");
74
80
  if (existsSync(headlessDir)) {
75
81
  let entries;
76
- try { entries = readdirSync(headlessDir); } catch { entries = []; }
82
+ try {
83
+ entries = readdirSync(headlessDir);
84
+ } catch {
85
+ entries = [];
86
+ }
77
87
 
78
88
  for (const entry of entries) {
79
89
  if (SKIP_FILES.has(entry)) continue;
@@ -84,7 +94,9 @@ export async function cleanupTmpFiles({ protectPaths = [] } = {}) {
84
94
  rmSync(full, { recursive: true, force: true });
85
95
  cleaned++;
86
96
  }
87
- } catch { /* 권한 에러 등 무시 */ }
97
+ } catch {
98
+ /* 권한 에러 등 무시 */
99
+ }
88
100
  }
89
101
  }
90
102
 
@@ -97,7 +109,9 @@ if (process.argv[1]) {
97
109
  if (fileURLToPath(import.meta.url) === process.argv[1]) {
98
110
  const cleaned = await cleanupTmpFiles();
99
111
  if (cleaned > 0) {
100
- process.stdout.write(JSON.stringify({ message: `tfx-cleanup: ${cleaned} files removed` }));
112
+ process.stdout.write(
113
+ JSON.stringify({ message: `tfx-cleanup: ${cleaned} files removed` }),
114
+ );
101
115
  }
102
116
  }
103
117
  }