triflux 8.11.2 → 8.12.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.
Files changed (103) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.ko.md +1 -1
  3. package/README.md +1 -1
  4. package/hooks/hooks.json +56 -51
  5. package/hub/pipeline/index.mjs +318 -318
  6. package/hub/routing/q-learning.mjs +2 -1
  7. package/hub/schema.sql +146 -146
  8. package/hub/team/cli/commands/kill.mjs +37 -37
  9. package/hub/team/cli/commands/start/index.mjs +3 -2
  10. package/hub/team/cli/commands/start/parse-args.mjs +5 -0
  11. package/hub/team/cli/commands/start/start-headless.mjs +2 -1
  12. package/hub/team/cli/commands/stop.mjs +31 -31
  13. package/hub/team/cli/commands/task.mjs +30 -30
  14. package/hub/team/cli/help.mjs +41 -40
  15. package/hub/team/cli/services/hub-client.mjs +208 -208
  16. package/hub/team/cli/services/native-control.mjs +118 -118
  17. package/hub/team/cli/services/runtime-mode.mjs +62 -62
  18. package/hub/team/cli/services/state-store.mjs +48 -48
  19. package/hub/team/dashboard-anchor.mjs +14 -0
  20. package/hub/team/dashboard.mjs +274 -274
  21. package/hub/team/headless.mjs +44 -19
  22. package/hub/team/native.mjs +649 -649
  23. package/hub/tools.mjs +554 -554
  24. package/hub/workers/delegator-mcp.mjs +21 -1
  25. package/package.json +1 -1
  26. package/scripts/__tests__/remote-spawn-transfer.test.mjs +117 -0
  27. package/scripts/__tests__/remote-spawn.test.mjs +78 -0
  28. package/scripts/cache-buildup.mjs +401 -0
  29. package/scripts/headless-guard.mjs +10 -3
  30. package/scripts/hub-ensure.mjs +120 -120
  31. package/scripts/lib/mcp-filter.mjs +720 -720
  32. package/scripts/lib/remote-spawn-transfer.mjs +196 -0
  33. package/scripts/mcp-check.mjs +237 -127
  34. package/scripts/mcp-gateway-ensure.mjs +6 -8
  35. package/scripts/mcp-gateway-integration-test.mjs +228 -0
  36. package/scripts/mcp-gateway-start.mjs +42 -16
  37. package/scripts/mcp-gateway-start.ps1 +17 -14
  38. package/scripts/mcp-gateway-verify.mjs +1 -1
  39. package/scripts/preflight-cache.mjs +137 -137
  40. package/scripts/remote-spawn.mjs +404 -40
  41. package/scripts/setup.mjs +18 -0
  42. package/scripts/tfx-route-worker.mjs +165 -165
  43. package/scripts/tfx-route.sh +85 -16
  44. package/scripts/token-snapshot.mjs +575 -575
  45. package/skills/tfx-analysis/SKILL.md +101 -101
  46. package/skills/tfx-autopilot/SKILL.md +112 -112
  47. package/skills/tfx-autoresearch/SKILL.md +1 -0
  48. package/skills/tfx-autoroute/SKILL.md +184 -184
  49. package/skills/tfx-codex-swarm/SKILL.md +449 -0
  50. package/skills/tfx-codex-swarm/evals/evals.json +26 -0
  51. package/skills/tfx-codex-swarm-workspace/iteration-1/benchmark.json +33 -0
  52. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/eval_metadata.json +42 -0
  53. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/grading.json +11 -0
  54. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/outputs/analysis.md +87 -0
  55. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/outputs/classification.md +35 -0
  56. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/outputs/commands.sh +275 -0
  57. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/outputs/routing.md +56 -0
  58. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/timing.json +5 -0
  59. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/grading.json +11 -0
  60. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/outputs/analysis.md +92 -0
  61. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/outputs/classification.md +71 -0
  62. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/outputs/commands.sh +264 -0
  63. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/outputs/routing.md +113 -0
  64. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/timing.json +5 -0
  65. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/eval_metadata.json +32 -0
  66. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/grading.json +9 -0
  67. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/outputs/analysis.md +96 -0
  68. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/outputs/classification.md +38 -0
  69. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/outputs/commands.sh +151 -0
  70. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/outputs/routing.md +51 -0
  71. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/timing.json +5 -0
  72. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/grading.json +9 -0
  73. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/outputs/analysis.md +127 -0
  74. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/outputs/classification.md +57 -0
  75. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/outputs/commands.sh +129 -0
  76. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/outputs/routing.md +84 -0
  77. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/timing.json +5 -0
  78. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/eval_metadata.json +27 -0
  79. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/grading.json +8 -0
  80. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/outputs/analysis.md +98 -0
  81. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/outputs/classification.md +65 -0
  82. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/outputs/commands.sh +123 -0
  83. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/outputs/routing.md +66 -0
  84. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/timing.json +5 -0
  85. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/grading.json +8 -0
  86. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/outputs/analysis.md +88 -0
  87. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/outputs/classification.md +40 -0
  88. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/outputs/commands.sh +130 -0
  89. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/outputs/routing.md +61 -0
  90. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/timing.json +5 -0
  91. package/skills/tfx-profile/SKILL.md +141 -51
  92. package/skills/tfx-qa/SKILL.md +117 -117
  93. package/skills/tfx-remote-spawn/references/hosts.json +16 -16
  94. package/skills/tfx-review/SKILL.md +51 -51
  95. package/skills/tfx-setup/SKILL.md +20 -0
  96. package/tui/gemini-profile.mjs +254 -0
  97. package/tui/setup.mjs +36 -1
  98. package/scripts/claude-logged.ps1 +0 -54
  99. package/scripts/demo-tui.mjs +0 -59
  100. package/skills/.omc/state/agent-replay-8f0e10a9-9693-4410-96f5-a6b07e8ed995.jsonl +0 -1
  101. package/skills/.omc/state/idle-notif-cooldown.json +0 -3
  102. package/skills/.omc/state/last-tool-error.json +0 -7
  103. package/skills/.omc/state/subagent-tracking.json +0 -7
@@ -1,165 +1,165 @@
1
- #!/usr/bin/env node
2
- // tfx-route-worker.mjs — tfx-route.sh용 subprocess worker 러너
3
-
4
- import { readFileSync, existsSync } from 'node:fs';
5
- import { dirname, resolve } from 'node:path';
6
- import { fileURLToPath, pathToFileURL } from 'node:url';
7
-
8
- const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
9
- const FACTORY_CANDIDATES = [
10
- resolve(SCRIPT_DIR, '../hub/workers/factory.mjs'),
11
- resolve(SCRIPT_DIR, './hub/workers/factory.mjs'),
12
- ];
13
-
14
- // MCP transport 실패 시 tfx-route.sh가 exec fallback을 수행할 수 있도록
15
- // CODEX_MCP_TRANSPORT_EXIT_CODE(70)으로 종료한다.
16
- const MCP_TRANSPORT_EXIT_CODE = 70;
17
-
18
- let createWorker = null;
19
-
20
- for (const candidate of FACTORY_CANDIDATES) {
21
- if (!existsSync(candidate)) continue;
22
- try {
23
- ({ createWorker } = await import(pathToFileURL(candidate).href));
24
- } catch (err) {
25
- // 의존성 누락 (예: @modelcontextprotocol/sdk) → fallback 가능하도록 exit 70
26
- if (err.code === 'ERR_MODULE_NOT_FOUND') {
27
- process.stderr.write(`[tfx-route-worker] 모듈 로드 실패: ${err.message}\n`);
28
- process.exit(MCP_TRANSPORT_EXIT_CODE);
29
- }
30
- throw err;
31
- }
32
- break;
33
- }
34
-
35
- if (!createWorker) {
36
- process.stderr.write('[tfx-route-worker] worker factory를 찾지 못했습니다.\n');
37
- process.exit(MCP_TRANSPORT_EXIT_CODE);
38
- }
39
-
40
- function parseArgs(argv) {
41
- const args = {
42
- allowedMcpServerNames: [],
43
- mcpConfig: [],
44
- };
45
-
46
- for (let index = 0; index < argv.length; index += 1) {
47
- const token = argv[index];
48
- const next = argv[index + 1];
49
-
50
- switch (token) {
51
- case '--type':
52
- args.type = next;
53
- index += 1;
54
- break;
55
- case '--command':
56
- args.command = next;
57
- index += 1;
58
- break;
59
- case '--command-args-json':
60
- args.commandArgsJson = next;
61
- index += 1;
62
- break;
63
- case '--model':
64
- args.model = next;
65
- index += 1;
66
- break;
67
- case '--timeout-ms':
68
- args.timeoutMs = Number(next);
69
- index += 1;
70
- break;
71
- case '--approval-mode':
72
- args.approvalMode = next;
73
- index += 1;
74
- break;
75
- case '--permission-mode':
76
- args.permissionMode = next;
77
- index += 1;
78
- break;
79
- case '--allow-dangerously-skip-permissions':
80
- args.allowDangerouslySkipPermissions = true;
81
- break;
82
- case '--allowed-mcp-server-name':
83
- args.allowedMcpServerNames.push(next);
84
- index += 1;
85
- break;
86
- case '--mcp-config':
87
- args.mcpConfig.push(next);
88
- index += 1;
89
- break;
90
- case '--cwd':
91
- args.cwd = next;
92
- index += 1;
93
- break;
94
- default:
95
- throw new Error(`Unknown argument: ${token}`);
96
- }
97
- }
98
-
99
- if (!args.type) {
100
- throw new Error('--type is required');
101
- }
102
-
103
- return args;
104
- }
105
-
106
- function parseJsonArray(raw, label) {
107
- if (!raw) return [];
108
- try {
109
- const parsed = JSON.parse(raw);
110
- if (!Array.isArray(parsed)) {
111
- throw new Error(`${label} must be a JSON array`);
112
- }
113
- return parsed.map((item) => String(item));
114
- } catch (error) {
115
- throw new Error(`${label} parse failed: ${error.message}`);
116
- }
117
- }
118
-
119
- function readPromptFromStdin() {
120
- return readFileSync(0, 'utf8');
121
- }
122
-
123
- function resolveDefaultMcpConfig(cwd) {
124
- const primary = resolve(cwd, '.claude', 'mcp.json');
125
- if (existsSync(primary)) return [primary];
126
- const legacy = resolve(cwd, '.mcp.json');
127
- if (existsSync(legacy)) return [legacy];
128
- process.stderr.write('[tfx-route-worker] warning: no MCP config found, hub unavailable\n');
129
- return [];
130
- }
131
-
132
- const args = parseArgs(process.argv.slice(2));
133
- const prompt = readPromptFromStdin();
134
-
135
- const worker = createWorker(args.type, {
136
- command: args.command,
137
- commandArgs: parseJsonArray(args.commandArgsJson, '--command-args-json'),
138
- model: args.model,
139
- timeoutMs: args.timeoutMs,
140
- approvalMode: args.approvalMode,
141
- permissionMode: args.permissionMode,
142
- allowDangerouslySkipPermissions: args.allowDangerouslySkipPermissions,
143
- allowedMcpServerNames: args.allowedMcpServerNames,
144
- mcpConfig: args.type === 'claude' && args.mcpConfig.length === 0
145
- ? resolveDefaultMcpConfig(args.cwd || process.cwd())
146
- : args.mcpConfig,
147
- cwd: args.cwd || process.cwd(),
148
- });
149
-
150
- try {
151
- const result = await worker.run(prompt);
152
- if (result.response) {
153
- process.stdout.write(result.response);
154
- if (!result.response.endsWith('\n')) process.stdout.write('\n');
155
- }
156
- } catch (error) {
157
- if (error.stderr) {
158
- process.stderr.write(String(error.stderr));
159
- if (!String(error.stderr).endsWith('\n')) process.stderr.write('\n');
160
- }
161
- process.stderr.write(`${error.message}\n`);
162
- process.exitCode = error.code === 'ETIMEDOUT' ? 124 : 1;
163
- } finally {
164
- try { await worker.stop(); } catch {}
165
- }
1
+ #!/usr/bin/env node
2
+ // tfx-route-worker.mjs — tfx-route.sh용 subprocess worker 러너
3
+
4
+ import { readFileSync, existsSync } from 'node:fs';
5
+ import { dirname, resolve } from 'node:path';
6
+ import { fileURLToPath, pathToFileURL } from 'node:url';
7
+
8
+ const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
9
+ const FACTORY_CANDIDATES = [
10
+ resolve(SCRIPT_DIR, '../hub/workers/factory.mjs'),
11
+ resolve(SCRIPT_DIR, './hub/workers/factory.mjs'),
12
+ ];
13
+
14
+ // MCP transport 실패 시 tfx-route.sh가 exec fallback을 수행할 수 있도록
15
+ // CODEX_MCP_TRANSPORT_EXIT_CODE(70)으로 종료한다.
16
+ const MCP_TRANSPORT_EXIT_CODE = 70;
17
+
18
+ let createWorker = null;
19
+
20
+ for (const candidate of FACTORY_CANDIDATES) {
21
+ if (!existsSync(candidate)) continue;
22
+ try {
23
+ ({ createWorker } = await import(pathToFileURL(candidate).href));
24
+ } catch (err) {
25
+ // 의존성 누락 (예: @modelcontextprotocol/sdk) → fallback 가능하도록 exit 70
26
+ if (err.code === 'ERR_MODULE_NOT_FOUND') {
27
+ process.stderr.write(`[tfx-route-worker] 모듈 로드 실패: ${err.message}\n`);
28
+ process.exit(MCP_TRANSPORT_EXIT_CODE);
29
+ }
30
+ throw err;
31
+ }
32
+ break;
33
+ }
34
+
35
+ if (!createWorker) {
36
+ process.stderr.write('[tfx-route-worker] worker factory를 찾지 못했습니다.\n');
37
+ process.exit(MCP_TRANSPORT_EXIT_CODE);
38
+ }
39
+
40
+ function parseArgs(argv) {
41
+ const args = {
42
+ allowedMcpServerNames: [],
43
+ mcpConfig: [],
44
+ };
45
+
46
+ for (let index = 0; index < argv.length; index += 1) {
47
+ const token = argv[index];
48
+ const next = argv[index + 1];
49
+
50
+ switch (token) {
51
+ case '--type':
52
+ args.type = next;
53
+ index += 1;
54
+ break;
55
+ case '--command':
56
+ args.command = next;
57
+ index += 1;
58
+ break;
59
+ case '--command-args-json':
60
+ args.commandArgsJson = next;
61
+ index += 1;
62
+ break;
63
+ case '--model':
64
+ args.model = next;
65
+ index += 1;
66
+ break;
67
+ case '--timeout-ms':
68
+ args.timeoutMs = Number(next);
69
+ index += 1;
70
+ break;
71
+ case '--approval-mode':
72
+ args.approvalMode = next;
73
+ index += 1;
74
+ break;
75
+ case '--permission-mode':
76
+ args.permissionMode = next;
77
+ index += 1;
78
+ break;
79
+ case '--allow-dangerously-skip-permissions':
80
+ args.allowDangerouslySkipPermissions = true;
81
+ break;
82
+ case '--allowed-mcp-server-name':
83
+ args.allowedMcpServerNames.push(next);
84
+ index += 1;
85
+ break;
86
+ case '--mcp-config':
87
+ args.mcpConfig.push(next);
88
+ index += 1;
89
+ break;
90
+ case '--cwd':
91
+ args.cwd = next;
92
+ index += 1;
93
+ break;
94
+ default:
95
+ throw new Error(`Unknown argument: ${token}`);
96
+ }
97
+ }
98
+
99
+ if (!args.type) {
100
+ throw new Error('--type is required');
101
+ }
102
+
103
+ return args;
104
+ }
105
+
106
+ function parseJsonArray(raw, label) {
107
+ if (!raw) return [];
108
+ try {
109
+ const parsed = JSON.parse(raw);
110
+ if (!Array.isArray(parsed)) {
111
+ throw new Error(`${label} must be a JSON array`);
112
+ }
113
+ return parsed.map((item) => String(item));
114
+ } catch (error) {
115
+ throw new Error(`${label} parse failed: ${error.message}`);
116
+ }
117
+ }
118
+
119
+ function readPromptFromStdin() {
120
+ return readFileSync(0, 'utf8');
121
+ }
122
+
123
+ function resolveDefaultMcpConfig(cwd) {
124
+ const primary = resolve(cwd, '.claude', 'mcp.json');
125
+ if (existsSync(primary)) return [primary];
126
+ const legacy = resolve(cwd, '.mcp.json');
127
+ if (existsSync(legacy)) return [legacy];
128
+ process.stderr.write('[tfx-route-worker] warning: no MCP config found, hub unavailable\n');
129
+ return [];
130
+ }
131
+
132
+ const args = parseArgs(process.argv.slice(2));
133
+ const prompt = readPromptFromStdin();
134
+
135
+ const worker = createWorker(args.type, {
136
+ command: args.command,
137
+ commandArgs: parseJsonArray(args.commandArgsJson, '--command-args-json'),
138
+ model: args.model,
139
+ timeoutMs: args.timeoutMs,
140
+ approvalMode: args.approvalMode,
141
+ permissionMode: args.permissionMode,
142
+ allowDangerouslySkipPermissions: args.allowDangerouslySkipPermissions,
143
+ allowedMcpServerNames: args.allowedMcpServerNames,
144
+ mcpConfig: args.type === 'claude' && args.mcpConfig.length === 0
145
+ ? resolveDefaultMcpConfig(args.cwd || process.cwd())
146
+ : args.mcpConfig,
147
+ cwd: args.cwd || process.cwd(),
148
+ });
149
+
150
+ try {
151
+ const result = await worker.run(prompt);
152
+ if (result.response) {
153
+ process.stdout.write(result.response);
154
+ if (!result.response.endsWith('\n')) process.stdout.write('\n');
155
+ }
156
+ } catch (error) {
157
+ if (error.stderr) {
158
+ process.stderr.write(String(error.stderr));
159
+ if (!String(error.stderr).endsWith('\n')) process.stderr.write('\n');
160
+ }
161
+ process.stderr.write(`${error.message}\n`);
162
+ process.exitCode = error.code === 'ETIMEDOUT' ? 124 : 1;
163
+ } finally {
164
+ try { await worker.stop(); } catch {}
165
+ }
@@ -38,8 +38,26 @@ else
38
38
  TIMEOUT_BIN="timeout" # Linux 기본
39
39
  fi
40
40
 
41
+ # ── 임시 디렉토리 정규화 ──
42
+ resolve_tmp_dir() {
43
+ local candidate=""
44
+ for candidate in "${TMPDIR:-}" "${TEMP:-}" "${TMP:-}" "/tmp"; do
45
+ [[ -n "$candidate" ]] || continue
46
+ if mkdir -p "$candidate" >/dev/null 2>&1; then
47
+ printf '%s\n' "$candidate"
48
+ return 0
49
+ fi
50
+ done
51
+
52
+ candidate="$(pwd)/.tfx-tmp"
53
+ mkdir -p "$candidate" >/dev/null 2>&1 || true
54
+ printf '%s\n' "$candidate"
55
+ }
56
+
57
+ TFX_TMP="$(resolve_tmp_dir)"
58
+
41
59
  # ── Async Job 디렉토리 ──
42
- TFX_JOBS_DIR="${TMPDIR:-/tmp}/tfx-jobs"
60
+ TFX_JOBS_DIR="${TFX_TMP}/tfx-jobs"
43
61
 
44
62
  # ── --job-status / --job-result 핸들러 (인자 파싱 전에 처리) ──
45
63
  if [[ "${1:-}" == "--job-status" ]]; then
@@ -148,13 +166,15 @@ CLAUDE_BIN="${CLAUDE_BIN:-$(command -v claude 2>/dev/null || echo claude)}"
148
166
  GEMINI_BIN_ARGS_JSON="${GEMINI_BIN_ARGS_JSON:-[]}"
149
167
  CLAUDE_BIN_ARGS_JSON="${CLAUDE_BIN_ARGS_JSON:-[]}"
150
168
 
169
+ # ── Gemini 프로필 경로 (Codex config.toml 대칭) ──
170
+ GEMINI_PROFILES_PATH="${GEMINI_PROFILES_PATH:-$(eval echo ~)/.gemini/triflux-profiles.json}"
171
+
151
172
  # ── 상수 ──
152
173
  MAX_STDOUT_BYTES=51200 # 50KB — Claude 컨텍스트 절약
153
174
  TIMESTAMP=$(date +%s)
154
175
  RUN_ID="${TIMESTAMP}-$$-${RANDOM}"
155
- STDERR_LOG="/tmp/tfx-route-${AGENT_TYPE}-${RUN_ID}-stderr.log"
156
- STDOUT_LOG="/tmp/tfx-route-${AGENT_TYPE}-${RUN_ID}-stdout.log"
157
- TFX_TMP="${TMPDIR:-/tmp}"
176
+ STDERR_LOG="${TFX_TMP}/tfx-route-${AGENT_TYPE}-${RUN_ID}-stderr.log"
177
+ STDOUT_LOG="${TFX_TMP}/tfx-route-${AGENT_TYPE}-${RUN_ID}-stdout.log"
158
178
 
159
179
  # ── 팀 환경변수 ──
160
180
  TFX_TEAM_NAME="${TFX_TEAM_NAME:-}"
@@ -593,6 +613,33 @@ codex_gte() {
593
613
  printf '%s\n%s' "$min" "$cur" | sort -V | head -1 | grep -q "^${min}$"
594
614
  }
595
615
 
616
+ # ── Gemini 프로필 해석 (Codex --profile 대칭) ──
617
+ _GEMINI_PROFILE_CACHE=""
618
+ resolve_gemini_profile() {
619
+ local profile="$1"
620
+ if [[ -z "$_GEMINI_PROFILE_CACHE" && -f "$GEMINI_PROFILES_PATH" ]]; then
621
+ _GEMINI_PROFILE_CACHE=$(cat "$GEMINI_PROFILES_PATH" 2>/dev/null || echo "{}")
622
+ fi
623
+ local result
624
+ result=$("$NODE_BIN" -e "
625
+ const name = process.argv[1];
626
+ const defaults = {
627
+ pro31: 'gemini-3.1-pro-preview',
628
+ flash3: 'gemini-3-flash-preview',
629
+ pro25: 'gemini-2.5-pro',
630
+ flash25: 'gemini-2.5-flash',
631
+ lite25: 'gemini-2.5-flash-lite'
632
+ };
633
+ try {
634
+ const cfg = JSON.parse(process.argv[2] || '{}');
635
+ const p = cfg.profiles?.[name];
636
+ if (p?.model) { process.stdout.write(p.model); return; }
637
+ } catch {}
638
+ process.stdout.write(defaults[name] || 'gemini-3.1-pro-preview');
639
+ " "$profile" "$_GEMINI_PROFILE_CACHE" 2>/dev/null)
640
+ echo "${result:-gemini-3.1-pro-preview}"
641
+ }
642
+
596
643
  # ── 라우팅 테이블 ──
597
644
  # CLI_TYPE/CLI_CMD: agent-map.json 단일 소스. 상세 설정: 아래 case 문.
598
645
  # 반환: CLI_TYPE, CLI_CMD, CLI_ARGS, CLI_EFFORT, DEFAULT_TIMEOUT, RUN_MODE, OPUS_OVERSIGHT
@@ -691,11 +738,11 @@ route_agent() {
691
738
 
692
739
  # ─── UI/문서 레인 ───
693
740
  designer)
694
- CLI_ARGS="-m gemini-3.1-pro-preview -y --prompt"
695
- CLI_EFFORT="pro"; DEFAULT_TIMEOUT=900; RUN_MODE="bg"; OPUS_OVERSIGHT="false" ;;
741
+ CLI_ARGS="-m $(resolve_gemini_profile pro31) -y --prompt"
742
+ CLI_EFFORT="pro31"; DEFAULT_TIMEOUT=900; RUN_MODE="bg"; OPUS_OVERSIGHT="false" ;;
696
743
  writer)
697
- CLI_ARGS="-m gemini-3-flash-preview -y --prompt"
698
- CLI_EFFORT="flash"; DEFAULT_TIMEOUT=900; RUN_MODE="bg"; OPUS_OVERSIGHT="false" ;;
744
+ CLI_ARGS="-m $(resolve_gemini_profile flash3) -y --prompt"
745
+ CLI_EFFORT="flash3"; DEFAULT_TIMEOUT=900; RUN_MODE="bg"; OPUS_OVERSIGHT="false" ;;
699
746
 
700
747
  # ─── 탐색 (Claude-native: Glob/Grep/Read 직접 접근) ───
701
748
  explore)
@@ -721,8 +768,8 @@ route_agent() {
721
768
  CLI_ARGS="exec --profile codex53_high ${codex_base}"
722
769
  CLI_EFFORT="codex53_high"; DEFAULT_TIMEOUT=1080; RUN_MODE="fg"; OPUS_OVERSIGHT="false" ;;
723
770
  gemini)
724
- CLI_ARGS="-m gemini-3.1-pro-preview -y --prompt"
725
- CLI_EFFORT="pro"; DEFAULT_TIMEOUT=900; RUN_MODE="bg"; OPUS_OVERSIGHT="false" ;;
771
+ CLI_ARGS="-m $(resolve_gemini_profile pro31) -y --prompt"
772
+ CLI_EFFORT="pro31"; DEFAULT_TIMEOUT=900; RUN_MODE="bg"; OPUS_OVERSIGHT="false" ;;
726
773
  claude)
727
774
  CLI_EFFORT="n/a"; DEFAULT_TIMEOUT=600; RUN_MODE="fg"; OPUS_OVERSIGHT="false" ;;
728
775
  # ─── agent-map.json에만 정의된 신규 에이전트 (CLI_TYPE별 기본값) ───
@@ -732,8 +779,8 @@ route_agent() {
732
779
  CLI_ARGS="exec --profile codex53_high ${codex_base}"
733
780
  CLI_EFFORT="codex53_high"; DEFAULT_TIMEOUT=1080; RUN_MODE="fg"; OPUS_OVERSIGHT="false" ;;
734
781
  gemini)
735
- CLI_ARGS="-m gemini-3.1-pro-preview -y --prompt"
736
- CLI_EFFORT="pro"; DEFAULT_TIMEOUT=900; RUN_MODE="bg"; OPUS_OVERSIGHT="false" ;;
782
+ CLI_ARGS="-m $(resolve_gemini_profile pro31) -y --prompt"
783
+ CLI_EFFORT="pro31"; DEFAULT_TIMEOUT=900; RUN_MODE="bg"; OPUS_OVERSIGHT="false" ;;
737
784
  claude-native)
738
785
  CLI_EFFORT="n/a"; DEFAULT_TIMEOUT=600; RUN_MODE="fg"; OPUS_OVERSIGHT="false" ;;
739
786
  esac ;;
@@ -821,15 +868,31 @@ apply_cli_mode() {
821
868
  fi ;;
822
869
  gemini)
823
870
  if [[ "$CLI_TYPE" == "codex" ]]; then
871
+ case "$AGENT_TYPE" in
872
+ verifier)
873
+ CLI_TYPE="claude-native"; CLI_CMD=""; CLI_ARGS=""
874
+ CLI_EFFORT="n/a"; DEFAULT_TIMEOUT=1200; RUN_MODE="fg"; OPUS_OVERSIGHT="false"
875
+ echo "[tfx-route] TFX_CLI_MODE=gemini: verifier는 claude-native 유지" >&2
876
+ return 0
877
+ ;;
878
+ test-engineer)
879
+ CLI_TYPE="claude-native"; CLI_CMD=""; CLI_ARGS=""
880
+ CLI_EFFORT="n/a"; DEFAULT_TIMEOUT=1200; RUN_MODE="bg"; OPUS_OVERSIGHT="false"
881
+ echo "[tfx-route] TFX_CLI_MODE=gemini: test-engineer는 claude-native 유지" >&2
882
+ return 0
883
+ ;;
884
+ esac
824
885
  CLI_TYPE="gemini"; CLI_CMD="gemini"
825
886
  case "$AGENT_TYPE" in
826
887
  executor|debugger|deep-executor|architect|planner|critic|analyst|\
827
- code-reviewer|security-reviewer|quality-reviewer|scientist-deep)
828
- CLI_ARGS="-m gemini-3.1-pro-preview -y --prompt"; CLI_EFFORT="pro" ;;
888
+ code-reviewer|security-reviewer|quality-reviewer|scientist-deep|designer)
889
+ CLI_ARGS="-m $(resolve_gemini_profile pro31) -y --prompt"; CLI_EFFORT="pro31" ;;
829
890
  build-fixer|spark)
830
- CLI_ARGS="-m gemini-3-flash-preview -y --prompt"; CLI_EFFORT="flash"; DEFAULT_TIMEOUT=180 ;;
891
+ CLI_ARGS="-m $(resolve_gemini_profile flash3) -y --prompt"; CLI_EFFORT="flash3"; DEFAULT_TIMEOUT=180 ;;
892
+ writer)
893
+ CLI_ARGS="-m $(resolve_gemini_profile flash3) -y --prompt"; CLI_EFFORT="flash3" ;;
831
894
  *)
832
- CLI_ARGS="-m gemini-3-flash-preview -y --prompt"; CLI_EFFORT="flash" ;;
895
+ CLI_ARGS="-m $(resolve_gemini_profile flash3) -y --prompt"; CLI_EFFORT="flash3" ;;
833
896
  esac
834
897
  echo "[tfx-route] TFX_CLI_MODE=gemini: $AGENT_TYPE → gemini($CLI_EFFORT)로 리매핑" >&2
835
898
  fi ;;
@@ -931,6 +994,9 @@ apply_verifier_override() {
931
994
  ;;
932
995
  claude)
933
996
  ORIGINAL_AGENT="${ORIGINAL_AGENT:-$AGENT_TYPE}"
997
+ CLI_TYPE="claude-native"
998
+ CLI_CMD=""
999
+ CLI_ARGS=""
934
1000
  CLI_EFFORT="n/a"; DEFAULT_TIMEOUT=1200; RUN_MODE="fg"; OPUS_OVERSIGHT="false"
935
1001
  echo "[tfx-route] TFX_VERIFIER_OVERRIDE=claude: verifier -> claude-native" >&2
936
1002
  ;;
@@ -1001,6 +1067,9 @@ resolve_mcp_policy() {
1001
1067
  fi
1002
1068
 
1003
1069
  available_servers=$(get_cached_servers "$CLI_TYPE")
1070
+ if [[ "$CLI_TYPE" == "codex" && "${TFX_CODEX_TRANSPORT:-auto}" != "mcp" ]]; then
1071
+ available_servers=""
1072
+ fi
1004
1073
  # Codex 0.115+: 미등록 서버에 config override(enabled=true/false 모두)를 보내면
1005
1074
  # "invalid transport" 에러 발생. 캐시 비어있으면 빈 문자열로 유지하여
1006
1075
  # mcp-filter가 override를 생성하지 않도록 한다.