@triflux/remote 10.50.1 → 10.51.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 (120) hide show
  1. package/cto/brief.mjs +0 -12
  2. package/cto/collect.mjs +16 -247
  3. package/cto/current.schema.json +4 -20
  4. package/cto/events.mjs +13 -294
  5. package/cto/hygiene.mjs +26 -346
  6. package/cto/lake-root.mjs +114 -16
  7. package/cto/status.mjs +33 -128
  8. package/hub/index.mjs +0 -8
  9. package/hub/store-adapter.mjs +1 -1
  10. package/hub/team/agent-map.json +0 -1
  11. package/hub/team/ansi.mjs +1 -35
  12. package/hub/team/backend.mjs +9 -145
  13. package/hub/team/cli/commands/attach.mjs +1 -17
  14. package/hub/team/cli/commands/control.mjs +12 -38
  15. package/hub/team/cli/commands/debug.mjs +1 -37
  16. package/hub/team/cli/commands/focus.mjs +5 -31
  17. package/hub/team/cli/commands/interrupt.mjs +6 -25
  18. package/hub/team/cli/commands/kill.mjs +1 -30
  19. package/hub/team/cli/commands/list.mjs +0 -16
  20. package/hub/team/cli/commands/send.mjs +6 -26
  21. package/hub/team/cli/commands/start/index.mjs +41 -133
  22. package/hub/team/cli/commands/start/parse-args.mjs +15 -40
  23. package/hub/team/cli/commands/start/start-headless.mjs +138 -105
  24. package/hub/team/cli/commands/start/start-mux.mjs +0 -3
  25. package/hub/team/cli/commands/status.mjs +2 -57
  26. package/hub/team/cli/commands/stop.mjs +14 -19
  27. package/hub/team/cli/help.mjs +4 -5
  28. package/hub/team/cli/manifest.mjs +5 -1
  29. package/hub/team/cli/render.mjs +0 -12
  30. package/hub/team/cli/services/hub-client.mjs +2 -88
  31. package/hub/team/cli/services/runtime-mode.mjs +27 -98
  32. package/hub/team/handoff.mjs +3 -34
  33. package/hub/team/headless.mjs +14 -755
  34. package/hub/team/orchestrator.mjs +4 -30
  35. package/hub/team/pane.mjs +6 -28
  36. package/hub/team/psmux.mjs +16 -403
  37. package/hub/team/remote-session.mjs +1 -2
  38. package/hub/team/session.mjs +8 -119
  39. package/hub/team/swarm-intent.mjs +0 -198
  40. package/hub/team/terminal-opener.mjs +11 -35
  41. package/hub/team/tui-core.mjs +1 -1
  42. package/hub/team/tui-viewer.mjs +2 -2
  43. package/hub/team/tui.mjs +8 -269
  44. package/hub/team/wt-manager.mjs +2 -2
  45. package/hub/workers/factory.mjs +4 -37
  46. package/hub/workers/lib/jsonrpc-ws-uds.mjs +0 -2
  47. package/package.json +1 -1
  48. package/scripts/lib/agent-route-policy.mjs +162 -14
  49. package/scripts/lib/claudemd-scanner.mjs +2 -199
  50. package/scripts/lib/cli-claude.mjs +0 -2
  51. package/scripts/lib/context.mjs +3 -19
  52. package/scripts/lib/env-probe.mjs +5 -64
  53. package/scripts/lib/gemini-profiles.mjs +2 -69
  54. package/scripts/lib/legacy-mcp-cleanup.mjs +14 -3
  55. package/scripts/lib/machine-profile.mjs +3 -17
  56. package/scripts/lib/mcp-filter.mjs +109 -53
  57. package/scripts/lib/mcp-health.mjs +2 -2
  58. package/scripts/lib/mcp-server-catalog.mjs +2 -2
  59. package/scripts/lib/psmux-info.mjs +25 -104
  60. package/scripts/lib/skill-template.mjs +0 -271
  61. package/cto/hygiene-actions.mjs +0 -355
  62. package/cto/hygiene-notify.mjs +0 -170
  63. package/hub/team/build-worker-prompt.mjs +0 -92
  64. package/hub/team/check-mcp-hub.mjs +0 -49
  65. package/hub/team/claude-native-bridge.mjs +0 -987
  66. package/hub/team/cli/commands/start/start-in-process.mjs +0 -50
  67. package/hub/team/cli/commands/start/start-wt.mjs +0 -86
  68. package/hub/team/cli/services/native-control.mjs +0 -145
  69. package/hub/team/codex-review.mjs +0 -520
  70. package/hub/team/conductor-mesh-bridge.mjs +0 -122
  71. package/hub/team/conductor.mjs +0 -1529
  72. package/hub/team/consensus-meta.mjs +0 -176
  73. package/hub/team/cto-pull.mjs +0 -31
  74. package/hub/team/daemon-pty-tmux-bridge.mjs +0 -146
  75. package/hub/team/dashboard.mjs +0 -322
  76. package/hub/team/event-log.mjs +0 -84
  77. package/hub/team/execution-mode.mjs +0 -324
  78. package/hub/team/extract-completion-payload.mjs +0 -110
  79. package/hub/team/headless-bridge-session.mjs +0 -17
  80. package/hub/team/health-probe.mjs +0 -435
  81. package/hub/team/interactive-native-launcher.mjs +0 -119
  82. package/hub/team/interactive-native-worker.mjs +0 -80
  83. package/hub/team/interactive-tui-transport.mjs +0 -479
  84. package/hub/team/launcher-template.mjs +0 -144
  85. package/hub/team/mcp-selector.mjs +0 -145
  86. package/hub/team/native-supervisor.mjs +0 -460
  87. package/hub/team/native.mjs +0 -727
  88. package/hub/team/recovery-store.mjs +0 -149
  89. package/hub/team/remote-probe.mjs +0 -292
  90. package/hub/team/remote-watcher.mjs +0 -506
  91. package/hub/team/runtime-strategy.mjs +0 -200
  92. package/hub/team/schemas/worker-completion.json +0 -39
  93. package/hub/team/sentinel-capture.mjs +0 -127
  94. package/hub/team/swarm-cli.mjs +0 -390
  95. package/hub/team/swarm-hypervisor.mjs +0 -2206
  96. package/hub/team/swarm-locks.mjs +0 -335
  97. package/hub/team/swarm-planner.mjs +0 -371
  98. package/hub/team/swarm-preflight.mjs +0 -366
  99. package/hub/team/swarm-reconciler.mjs +0 -242
  100. package/hub/team/tui-remote-adapter.mjs +0 -377
  101. package/hub/team/worker-completion-validator.mjs +0 -79
  102. package/hub/team/worktree-lifecycle.mjs +0 -639
  103. package/hub/team/wt-templates.json +0 -43
  104. package/hub/workers/antigravity-route-worker.mjs +0 -179
  105. package/hub/workers/gemini-worker.mjs +0 -217
  106. package/scripts/lib/agent-json.mjs +0 -27
  107. package/scripts/lib/async.mjs +0 -174
  108. package/scripts/lib/cli-agy.mjs +0 -85
  109. package/scripts/lib/cli-codex.mjs +0 -62
  110. package/scripts/lib/cli-gemini.mjs +0 -37
  111. package/scripts/lib/dynamic-route-cli.mjs +0 -107
  112. package/scripts/lib/env.mjs +0 -14
  113. package/scripts/lib/hub.mjs +0 -48
  114. package/scripts/lib/pid.mjs +0 -63
  115. package/scripts/lib/quota.mjs +0 -47
  116. package/scripts/lib/skill-state.mjs +0 -220
  117. package/scripts/lib/team.mjs +0 -75
  118. package/scripts/lib/timeout.mjs +0 -66
  119. package/scripts/lib/tmp.mjs +0 -26
  120. package/scripts/lib/toml.mjs +0 -132
@@ -30,36 +30,10 @@ const DEFAULT_GEMINI_PROFILES = {
30
30
  },
31
31
  };
32
32
 
33
- // 1회 마이그레이션 테이블: 옛 ID 형식 모델값 → agy display name.
34
- // null = 카탈로그에서 빠진 모델(2.5 계열, 3 Flash) → 해당 프로필 제거 대상.
35
- const LEGACY_MODEL_MIGRATION = {
36
- "gemini-3.1-pro-preview": "Gemini 3.1 Pro (High)",
37
- "gemini-3-flash-preview": null,
38
- "Gemini 3 Flash": null,
39
- "gemini-2.5-pro": null,
40
- "gemini-2.5-flash": null,
41
- "gemini-2.5-flash-lite": null,
42
- };
43
- // 과거 setup 이 자동 생성했던 프로필 이름. 정책상 더 쓰지 않으므로 제거한다.
44
- // 사용자가 직접 이름 붙인 프로필은 건드리지 않는다.
45
- const LEGACY_PROFILE_NAMES = [
46
- "pro25",
47
- "flash25",
48
- "lite25",
49
- "flash3",
50
- "pro31",
51
- "pro31_low",
52
- "flash35",
53
- "flash35_high",
54
- "flash35_low",
55
- ];
56
- // 과거 setup 이 자동 생성한 기본 model 값. 사용자가 고른 값이 아니므로 새 기본으로 올린다.
57
- const LEGACY_DEFAULT_MODELS = new Set(["Gemini 3.5 Flash (Medium)"]);
58
-
59
33
  // ── 용도별 effort (SSOT) ──
60
34
  // 한 세대(3.8 Flash)만 쓰고 역할에 따라 effort 를 나눈다. 키는 tfx 에이전트/역할 이름
61
35
  // (agent-map.json 의 에이전트, tfx multi --assign 의 role). bash(tfx-route.sh)와
62
- // node(execution-mode, gemini-adapter, cli-agy)가 모두 이 표를 읽는다.
36
+ // 실행 모드(execution-mode)가 이 표를 읽는다.
63
37
  // High : 판단이 결과를 좌우하는 역할 (검토, 설계, 디버깅, 분석)
64
38
  // Medium : 생성/작성/실행 (기본값)
65
39
  // Low : 분류, 요약, 추출, 번역처럼 짧고 기계적인 작업
@@ -182,40 +156,6 @@ function ensureGeminiProfiles({
182
156
  )
183
157
  cfg.profiles = {};
184
158
 
185
- // ── 1회 마이그레이션: deprecated 2.5 프로필 prune + 옛 ID 형식 → display name ──
186
- // merge-only 로직만으로는 기존 사용자 파일에 남은 stale 프로필/옛 모델 ID 가
187
- // 정리되지 않으므로, 신규 default 를 채우기 전에 마이그레이션을 먼저 적용한다.
188
- let migrated = false;
189
- for (const legacy of LEGACY_PROFILE_NAMES) {
190
- if (cfg.profiles[legacy]) {
191
- delete cfg.profiles[legacy];
192
- migrated = true;
193
- }
194
- }
195
- for (const [pname, pval] of Object.entries(cfg.profiles)) {
196
- const mid = typeof pval === "string" ? pval : pval?.model;
197
- if (mid && Object.hasOwn(LEGACY_MODEL_MIGRATION, mid)) {
198
- const repl = LEGACY_MODEL_MIGRATION[mid];
199
- if (repl === null) {
200
- delete cfg.profiles[pname];
201
- } else if (typeof pval === "string") {
202
- cfg.profiles[pname] = repl;
203
- } else {
204
- cfg.profiles[pname].model = repl;
205
- }
206
- migrated = true;
207
- }
208
- }
209
- if (
210
- typeof cfg.model === "string" &&
211
- (Object.hasOwn(LEGACY_MODEL_MIGRATION, cfg.model) ||
212
- LEGACY_DEFAULT_MODELS.has(cfg.model))
213
- ) {
214
- // 옛 ID 형식/이전 세대 자동 생성 기본값은 새 기본(DEFAULT)으로 정규화한다.
215
- cfg.model = DEFAULT_GEMINI_PROFILES.model;
216
- migrated = true;
217
- }
218
-
219
159
  let added = 0;
220
160
  for (const [name, value] of Object.entries(
221
161
  DEFAULT_GEMINI_PROFILES.profiles,
@@ -227,12 +167,7 @@ function ensureGeminiProfiles({
227
167
  }
228
168
  if (!cfg.model) cfg.model = DEFAULT_GEMINI_PROFILES.model;
229
169
 
230
- if (added > 0 || migrated) {
231
- if (migrated) {
232
- try {
233
- copyFileSync(profilesPath, profilesPath + `.bak.${Date.now()}`);
234
- } catch {}
235
- }
170
+ if (added > 0) {
236
171
  writeFileSync(profilesPath, JSON.stringify(cfg, null, 2) + "\n", {
237
172
  encoding: "utf8",
238
173
  mode: 0o600,
@@ -260,8 +195,6 @@ function ensureGeminiProfiles({
260
195
  export {
261
196
  DEFAULT_GEMINI_PROFILES,
262
197
  ensureGeminiProfiles,
263
- GEMINI_DEFAULT_PURPOSE_PROFILE,
264
- GEMINI_PROFILE_BY_PURPOSE,
265
198
  resolveGeminiModel,
266
199
  resolveGeminiProfileForPurpose,
267
200
  };
@@ -143,7 +143,14 @@ function envWarning(server, home, env, warnings) {
143
143
  }
144
144
  }
145
145
 
146
- function patchJson(original, file, home, env, warnings) {
146
+ function patchJson(
147
+ original,
148
+ file,
149
+ home,
150
+ env,
151
+ warnings,
152
+ { withEnv = true } = {},
153
+ ) {
147
154
  const data = JSON.parse(original);
148
155
  if (!data || typeof data !== "object" || Array.isArray(data))
149
156
  throw new Error(`${file}: JSON 객체가 아닙니다`);
@@ -190,7 +197,7 @@ function patchJson(original, file, home, env, warnings) {
190
197
  ...kept,
191
198
  command: server.command,
192
199
  args: server.args,
193
- ...(server.envVars.length
200
+ ...(withEnv && server.envVars.length
194
201
  ? {
195
202
  env: {
196
203
  ...kept.env,
@@ -710,6 +717,8 @@ export function cleanupLegacyMcp({
710
717
  [join(repoRoot, ".mcp.json"), "json"],
711
718
  [join(home, ".codex/config.toml"), "toml"],
712
719
  [join(home, ".gemini/settings.json"), "json"],
720
+ // agy 는 \${VAR} 참조를 풀지 않으므로 env 없이 바꾸고 셸 환경을 물려받게 한다.
721
+ [join(home, ".gemini/config/mcp_config.json"), "agy"],
713
722
  ];
714
723
  const plans = [];
715
724
  let blocked = false;
@@ -721,7 +730,9 @@ export function cleanupLegacyMcp({
721
730
  const plan =
722
731
  kind === "toml"
723
732
  ? patchToml(original, file, home, env, result.warnings)
724
- : patchJson(original, file, home, env, result.warnings);
733
+ : patchJson(original, file, home, env, result.warnings, {
734
+ withEnv: kind !== "agy",
735
+ });
725
736
  if (plan.blocked) blocked = true;
726
737
  if (plan.count) plans.push({ file, target, original, ...plan });
727
738
  } catch {
@@ -14,7 +14,7 @@ import { readFileSync } from "node:fs";
14
14
  import { homedir } from "node:os";
15
15
  import { join, resolve } from "node:path";
16
16
 
17
- export const MACHINE_PROFILE_FILENAME = "machine-profile.env";
17
+ const MACHINE_PROFILE_FILENAME = "machine-profile.env";
18
18
 
19
19
  export const MACHINE_PROFILE_KEYS = Object.freeze([
20
20
  "TFX_MACHINE_PROFILE_VERSION",
@@ -28,9 +28,9 @@ export const MACHINE_PROFILE_KEYS = Object.freeze([
28
28
  "TFX_STALL_KILL",
29
29
  ]);
30
30
 
31
- export const MACHINE_PROFILE_KEY_SET = new Set(MACHINE_PROFILE_KEYS);
31
+ const MACHINE_PROFILE_KEY_SET = new Set(MACHINE_PROFILE_KEYS);
32
32
 
33
- export const MACHINE_PROFILE_VALUE_RE = /^[A-Za-z0-9_.-]+$/u;
33
+ const MACHINE_PROFILE_VALUE_RE = /^[A-Za-z0-9_.-]+$/u;
34
34
 
35
35
  // CLI 이름과 disable 키의 대응. 이 두 개가 TFX_DISABLE_* SSOT 의 전부다.
36
36
  const CLI_DISABLE_KEYS = Object.freeze({
@@ -248,20 +248,6 @@ export function normalizeCliName(cli) {
248
248
  return CLI_ALIASES[key] || null;
249
249
  }
250
250
 
251
- /**
252
- * 세션 문맥 주입용 한 줄. 형식은 고정이며 소비자가 파싱하지 않고 그대로 보여준다.
253
- *
254
- * @param {ReturnType<typeof resolveCliPolicy>} policy
255
- * @returns {string}
256
- */
257
- export function formatCliPolicyLine(policy) {
258
- const codex = policy?.codexDisabled ? "off" : "on";
259
- const antigravity = policy?.antigravityDisabled ? "off" : "on";
260
- const codexSource = policy?.source?.codex || "default";
261
- const antigravitySource = policy?.source?.antigravity || "default";
262
- return `cli-policy: codex=${codex} antigravity=${antigravity} (SSOT: TFX_DISABLE_*; codex<-${codexSource}, antigravity<-${antigravitySource})`;
263
- }
264
-
265
251
  /**
266
252
  * 차단된 CLI 실행 요청에 쓸 오류를 만든다. throw 는 호출자가 한다. 정책상 허용된
267
253
  * CLI 이거나 모르는 이름이면 null 을 준다.
@@ -15,10 +15,6 @@ import {
15
15
  uniqueStrings,
16
16
  } from "./mcp-server-catalog.mjs";
17
17
 
18
- export const KNOWN_MCP_SERVERS = Object.freeze(
19
- Object.keys(MCP_SERVER_TOOL_CATALOG),
20
- );
21
-
22
18
  const SEARCH_INTENT_PATTERNS = Object.freeze([
23
19
  /\b(search|web|browse|look ?up|find|latest|recent|news|current|today|release(?: note)?s?|changelog|announcement|pricing|status|verify|fact[- ]?check)\b/i,
24
20
  /(검색|웹|브라우즈|찾아|조회|최신|최근|뉴스|현재|오늘|릴리즈|배포|변경사항|공지|가격|상태|검증)/u,
@@ -145,7 +141,7 @@ const PROFILE_DEFINITIONS = Object.freeze({
145
141
  * 파이프라인 단계별 MCP 서버/도구 제한 (post-filter).
146
142
  * role-based 프로필 위에 추가 적용. 빈 배열 = 전체 차단, 미정의 = 제한 없음.
147
143
  */
148
- export const PHASE_OVERRIDES = Object.freeze({
144
+ const PHASE_OVERRIDES = Object.freeze({
149
145
  plan: Object.freeze({
150
146
  description: "계획 단계: 읽기 전용 탐색만 허용",
151
147
  allowedServers: Object.freeze(["context7"]),
@@ -166,7 +162,7 @@ export const PHASE_OVERRIDES = Object.freeze({
166
162
  }),
167
163
  });
168
164
 
169
- export const LEGACY_PROFILE_ALIASES = Object.freeze({
165
+ const LEGACY_PROFILE_ALIASES = Object.freeze({
170
166
  implement: "executor",
171
167
  analyze: "analyze",
172
168
  review: "reviewer",
@@ -257,30 +253,51 @@ function loadInventory(inventoryFile = "") {
257
253
  }
258
254
  }
259
255
 
256
+ function availableServersFromInventory(inventory, cliType) {
257
+ const servers = inventory?.[cliType]?.servers || [];
258
+ return uniqueStrings(
259
+ servers
260
+ .filter((server) => ["enabled", "configured"].includes(server.status))
261
+ .map((server) => server.name),
262
+ );
263
+ }
264
+
265
+ function codexServersFromConfig(configFile) {
266
+ if (!configFile) return [];
267
+ try {
268
+ return uniqueStrings(
269
+ readFileSync(configFile, "utf8")
270
+ .split(/\r?\n/)
271
+ .map((line) => /^\[mcp_servers\.([^\].]+)\]$/.exec(line)?.[1])
272
+ .filter(Boolean),
273
+ ).sort();
274
+ } catch {
275
+ return [];
276
+ }
277
+ }
278
+
260
279
  function buildInventoryIndex(inventory = null) {
261
280
  const index = new Map();
262
281
  if (!inventory || typeof inventory !== "object") return index;
263
282
 
264
- for (const client of ["codex", "gemini"]) {
265
- const servers = Array.isArray(inventory[client]?.servers)
266
- ? inventory[client].servers
267
- : [];
268
- for (const server of servers) {
269
- if (!server || typeof server.name !== "string" || !server.name.trim())
270
- continue;
271
- const name = server.name.trim();
272
- const previous = index.get(name) || {};
273
- index.set(name, {
274
- ...previous,
275
- tool_count: Number.isFinite(server.tool_count)
276
- ? Math.max(previous.tool_count ?? 0, Math.trunc(server.tool_count))
277
- : previous.tool_count,
278
- domain_tags: uniqueStrings([
279
- ...(Array.isArray(previous.domain_tags) ? previous.domain_tags : []),
280
- ...(Array.isArray(server.domain_tags) ? server.domain_tags : []),
281
- ]),
282
- });
283
- }
283
+ const servers = Array.isArray(inventory.codex?.servers)
284
+ ? inventory.codex.servers
285
+ : [];
286
+ for (const server of servers) {
287
+ if (!server || typeof server.name !== "string" || !server.name.trim())
288
+ continue;
289
+ const name = server.name.trim();
290
+ const previous = index.get(name) || {};
291
+ index.set(name, {
292
+ ...previous,
293
+ tool_count: Number.isFinite(server.tool_count)
294
+ ? Math.max(previous.tool_count ?? 0, Math.trunc(server.tool_count))
295
+ : previous.tool_count,
296
+ domain_tags: uniqueStrings([
297
+ ...(Array.isArray(previous.domain_tags) ? previous.domain_tags : []),
298
+ ...(Array.isArray(server.domain_tags) ? server.domain_tags : []),
299
+ ]),
300
+ });
284
301
  }
285
302
 
286
303
  return index;
@@ -498,7 +515,7 @@ export function resolveMcpProfile(agentType = "", requestedProfile = "auto") {
498
515
  return normalized === "auto" ? resolveAutoProfile(agentType) : normalized;
499
516
  }
500
517
 
501
- export function parseAvailableServers(rawAvailableServers = "") {
518
+ function parseAvailableServers(rawAvailableServers = "") {
502
519
  if (Array.isArray(rawAvailableServers))
503
520
  return uniqueStrings(rawAvailableServers);
504
521
  if (typeof rawAvailableServers !== "string" || !rawAvailableServers.trim())
@@ -554,15 +571,21 @@ function applyManifestFilter(servers) {
554
571
  return servers.filter((s) => enabled.has(s));
555
572
  }
556
573
 
557
- export function resolveAllowedServers(options = {}) {
574
+ function resolveAllowedServers(options = {}) {
558
575
  const resolvedProfile = resolveMcpProfile(
559
576
  options.agentType,
560
577
  options.requestedProfile,
561
578
  );
562
579
  const profile = getProfileDefinition(resolvedProfile);
563
580
  const availableServers = parseAvailableServers(options.availableServers);
564
- const inventory = options.inventory || loadInventory(options.inventoryFile);
565
- const inventoryIndex = buildInventoryIndex(inventory);
581
+ const inventory =
582
+ options.inventory !== undefined
583
+ ? options.inventory
584
+ : loadInventory(options.inventoryFile);
585
+ const inventoryIndex =
586
+ options.inventoryIndex instanceof Map
587
+ ? options.inventoryIndex
588
+ : buildInventoryIndex(inventory);
566
589
  const baseServers = availableServers.length
567
590
  ? profile.allowedServers.filter((server) =>
568
591
  availableServers.includes(server),
@@ -585,8 +608,14 @@ export function buildPromptHint(options = {}) {
585
608
  );
586
609
  if (resolvedProfile === "none") return "";
587
610
 
588
- const inventory = options.inventory || loadInventory(options.inventoryFile);
589
- const inventoryIndex = buildInventoryIndex(inventory);
611
+ const inventory =
612
+ options.inventory !== undefined
613
+ ? options.inventory
614
+ : loadInventory(options.inventoryFile);
615
+ const inventoryIndex =
616
+ options.inventoryIndex instanceof Map
617
+ ? options.inventoryIndex
618
+ : buildInventoryIndex(inventory);
590
619
  const allowedServers = resolveAllowedServers({
591
620
  ...options,
592
621
  inventory,
@@ -636,16 +665,7 @@ export function buildPromptHint(options = {}) {
636
665
  .join(" ");
637
666
  }
638
667
 
639
- export function getGeminiAllowedServers(options = {}) {
640
- return resolveAllowedServers(options);
641
- }
642
-
643
668
  export function getCodexMcpConfig(options = {}) {
644
- const allowedServers = new Set(resolveAllowedServers(options));
645
- const resolvedProfile = resolveMcpProfile(
646
- options.agentType,
647
- options.requestedProfile,
648
- );
649
669
  // Codex에 실제 등록된 서버만 대상으로 config override 생성.
650
670
  // 미등록 서버에 enabled=false를 보내면 "invalid transport" 에러 발생.
651
671
  const registeredServers = parseAvailableServers(options.availableServers);
@@ -654,6 +674,11 @@ export function getCodexMcpConfig(options = {}) {
654
674
  if (registeredServers.length === 0) {
655
675
  return { mcp_servers: {} };
656
676
  }
677
+ const allowedServers = new Set(resolveAllowedServers(options));
678
+ const resolvedProfile = resolveMcpProfile(
679
+ options.agentType,
680
+ options.requestedProfile,
681
+ );
657
682
  const targetServers = registeredServers;
658
683
 
659
684
  if (resolvedProfile === "none") {
@@ -712,9 +737,27 @@ export function getCodexConfigOverrides(options = {}) {
712
737
  }
713
738
 
714
739
  export function buildMcpPolicy(options = {}) {
715
- const inventory = options.inventory || loadInventory(options.inventoryFile);
740
+ const inventory =
741
+ options.inventory !== undefined
742
+ ? options.inventory
743
+ : loadInventory(options.inventoryFile);
716
744
  const inventoryIndex = buildInventoryIndex(inventory);
717
- const resolvedOptions = { ...options, inventory, inventoryIndex };
745
+ let availableServers = options.availableServers;
746
+ if (availableServers === undefined) {
747
+ availableServers = availableServersFromInventory(
748
+ inventory,
749
+ options.cliType,
750
+ );
751
+ if (availableServers.length === 0 && options.cliType === "codex") {
752
+ availableServers = codexServersFromConfig(options.codexConfig);
753
+ }
754
+ }
755
+ const resolvedOptions = {
756
+ ...options,
757
+ availableServers,
758
+ inventory,
759
+ inventoryIndex,
760
+ };
718
761
  const resolvedProfile = resolveMcpProfile(
719
762
  options.agentType,
720
763
  options.requestedProfile,
@@ -730,6 +773,12 @@ export function buildMcpPolicy(options = {}) {
730
773
  allowedServers = allowedServers.filter((s) => !blocked.has(s));
731
774
  }
732
775
 
776
+ // 플러그인 서버는 transport 설정이 없어 CLI override를 받으면 부팅이 실패한다.
777
+ const codexOptions = {
778
+ ...resolvedOptions,
779
+ availableServers: codexServersFromConfig(options.codexConfig),
780
+ };
781
+
733
782
  return {
734
783
  requestedProfile:
735
784
  typeof options.requestedProfile === "string" && options.requestedProfile
@@ -739,9 +788,8 @@ export function buildMcpPolicy(options = {}) {
739
788
  resolvedPhase: phase || null,
740
789
  allowedServers,
741
790
  hint,
742
- geminiAllowedServers: getGeminiAllowedServers(resolvedOptions),
743
- codexConfig: getCodexMcpConfig(resolvedOptions),
744
- codexConfigOverrides: getCodexConfigOverrides(resolvedOptions),
791
+ codexConfig: getCodexMcpConfig(codexOptions),
792
+ codexConfigOverrides: getCodexConfigOverrides(codexOptions),
745
793
  };
746
794
  }
747
795
 
@@ -753,25 +801,25 @@ function shellArray(name, values) {
753
801
  return `${name}=(${values.map((value) => shellEscape(value)).join(" ")})`;
754
802
  }
755
803
 
756
- export function toDelimited(policy) {
804
+ function toDelimited(policy) {
757
805
  const RS = "\x1e";
758
806
  return [
759
807
  policy.requestedProfile,
760
808
  policy.resolvedProfile,
761
809
  policy.hint,
762
- policy.geminiAllowedServers.join(","),
763
- policy.codexConfigOverrides.flatMap((o) => ["-c", o]).join(","),
810
+ policy.allowedServers.join(","),
811
+ policy.codexConfigOverrides.flatMap((o) => ["-c", o]).join("\x1f"),
764
812
  JSON.stringify(policy.codexConfig),
765
813
  policy.resolvedPhase || "",
766
814
  ].join(RS);
767
815
  }
768
816
 
769
- export function toShellExports(policy) {
817
+ function toShellExports(policy) {
770
818
  const lines = [
771
819
  `MCP_PROFILE_REQUESTED=${shellEscape(policy.requestedProfile)}`,
772
820
  `MCP_RESOLVED_PROFILE=${shellEscape(policy.resolvedProfile)}`,
773
821
  `MCP_HINT=${shellEscape(policy.hint)}`,
774
- shellArray("GEMINI_ALLOWED_SERVERS", policy.geminiAllowedServers),
822
+ shellArray("ALLOWED_MCP_SERVERS", policy.allowedServers),
775
823
  shellArray(
776
824
  "CODEX_CONFIG_FLAGS",
777
825
  policy.codexConfigOverrides.flatMap((override) => ["-c", override]),
@@ -789,8 +837,10 @@ function parseCliArgs(argv) {
789
837
  command: "json",
790
838
  agentType: "",
791
839
  requestedProfile: "auto",
792
- availableServers: [],
840
+ availableServers: undefined,
793
841
  inventoryFile: "",
842
+ cliType: "",
843
+ codexConfig: "",
794
844
  searchTool: "",
795
845
  taskText: "",
796
846
  workerIndex: undefined,
@@ -821,6 +871,12 @@ function parseCliArgs(argv) {
821
871
  case "--available":
822
872
  args.availableServers = parseAvailableServers(next());
823
873
  break;
874
+ case "--cli-type":
875
+ args.cliType = next();
876
+ break;
877
+ case "--codex-config":
878
+ args.codexConfig = next();
879
+ break;
824
880
  case "--inventory-file":
825
881
  args.inventoryFile = next();
826
882
  break;
@@ -844,7 +900,7 @@ function parseCliArgs(argv) {
844
900
  return args;
845
901
  }
846
902
 
847
- export async function runCli(argv = process.argv.slice(2)) {
903
+ async function runCli(argv = process.argv.slice(2)) {
848
904
  let args;
849
905
  try {
850
906
  args = parseCliArgs(argv);
@@ -224,7 +224,7 @@ export function parseMcpServersFromToml(content = "") {
224
224
  return servers;
225
225
  }
226
226
 
227
- export function readMcpServers(configPath = DEFAULT_CONFIG_PATH) {
227
+ function readMcpServers(configPath = DEFAULT_CONFIG_PATH) {
228
228
  if (!existsSync(configPath)) return {};
229
229
  try {
230
230
  const content = readFileSync(configPath, "utf8");
@@ -754,7 +754,7 @@ function renderOutput(results, source, format) {
754
754
  return JSON.stringify({ source, healthy, dead, results }, null, 2);
755
755
  }
756
756
 
757
- export async function runCli(argv = process.argv.slice(2)) {
757
+ async function runCli(argv = process.argv.slice(2)) {
758
758
  let args;
759
759
  try {
760
760
  args = parseCliArgs(argv);
@@ -296,7 +296,7 @@ export function uniqueStrings(values = []) {
296
296
  ];
297
297
  }
298
298
 
299
- export function inferDomainTagsFromText(text = "") {
299
+ function inferDomainTagsFromText(text = "") {
300
300
  if (typeof text !== "string" || !text.trim()) return [];
301
301
  const normalized = text.toLocaleLowerCase();
302
302
  const matched = [];
@@ -314,7 +314,7 @@ export function inferDomainTagsFromText(text = "") {
314
314
  return uniqueStrings(matched);
315
315
  }
316
316
 
317
- export function getDefaultServerMetadata(serverName = "") {
317
+ function getDefaultServerMetadata(serverName = "") {
318
318
  const toolCount = MCP_SERVER_TOOL_CATALOG[serverName]?.length || 0;
319
319
  const domainTags = uniqueStrings([
320
320
  ...(MCP_SERVER_DOMAIN_TAGS[serverName] || []),