@triflux/core 10.50.2 → 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 (46) hide show
  1. package/hooks/agy-session-hook.mjs +11 -56
  2. package/hooks/codex-session-hook.mjs +11 -101
  3. package/hub/bridge.mjs +1 -79
  4. package/hub/cli-adapter-base.mjs +12 -90
  5. package/hub/codex-adapter.mjs +0 -1
  6. package/hub/codex-preflight.mjs +5 -28
  7. package/hub/index.mjs +1 -1
  8. package/hub/lib/known-errors.json +2 -2
  9. package/hub/lib/process-utils.mjs +0 -123
  10. package/hub/lib/spawn-trace.mjs +3 -2
  11. package/hub/lib/timeout-defaults.mjs +0 -1
  12. package/hub/role-contract.mjs +0 -2
  13. package/hud/cli-policy.mjs +0 -22
  14. package/hud/constants.mjs +11 -26
  15. package/hud/hud-qos-status.mjs +23 -51
  16. package/hud/providers/gemini.mjs +105 -20
  17. package/hud/renderers.mjs +50 -104
  18. package/hud/utils.mjs +2 -2
  19. package/package.json +1 -3
  20. package/scripts/lib/agent-route-policy.mjs +162 -14
  21. package/scripts/lib/claudemd-scanner.mjs +2 -199
  22. package/scripts/lib/cli-claude.mjs +0 -2
  23. package/scripts/lib/context.mjs +3 -19
  24. package/scripts/lib/env-probe.mjs +5 -64
  25. package/scripts/lib/gemini-profiles.mjs +2 -69
  26. package/scripts/lib/machine-profile.mjs +3 -17
  27. package/scripts/lib/mcp-filter.mjs +109 -53
  28. package/scripts/lib/mcp-health.mjs +2 -2
  29. package/scripts/lib/mcp-server-catalog.mjs +2 -2
  30. package/scripts/lib/psmux-info.mjs +11 -59
  31. package/scripts/lib/session-presence.mjs +1 -1
  32. package/scripts/lib/skill-template.mjs +0 -245
  33. package/hub/dynamic-routing-engine.mjs +0 -511
  34. package/hub/lib/risk-tier.mjs +0 -53
  35. package/hub/lib/ssh-command.mjs +0 -231
  36. package/hub/lib/staged-file-detect.mjs +0 -43
  37. package/hub/lib/tfx-route-args.mjs +0 -353
  38. package/hub/routing-snapshot.mjs +0 -261
  39. package/mesh/index.mjs +0 -69
  40. package/mesh/mesh-budget.mjs +0 -137
  41. package/mesh/mesh-heartbeat.mjs +0 -100
  42. package/mesh/mesh-protocol.mjs +0 -96
  43. package/mesh/mesh-queue.mjs +0 -163
  44. package/mesh/mesh-registry.mjs +0 -78
  45. package/mesh/mesh-router.mjs +0 -77
  46. package/scripts/lib/dynamic-route-cli.mjs +0 -107
@@ -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] || []),
@@ -1,36 +1,36 @@
1
1
  import { execFileSync } from "node:child_process";
2
2
 
3
- export const PSMUX_RECOMMENDED_VERSION = "3.3.1";
4
- export const PSMUX_REQUIRED_COMMANDS = [
3
+ const PSMUX_RECOMMENDED_VERSION = "3.3.1";
4
+ const PSMUX_REQUIRED_COMMANDS = [
5
5
  "new-session",
6
6
  "attach-session",
7
7
  "kill-session",
8
8
  "capture-pane",
9
9
  ];
10
10
 
11
- export const PSMUX_OPTIONAL_COMMANDS = ["detach-client"];
11
+ const PSMUX_OPTIONAL_COMMANDS = ["detach-client"];
12
12
 
13
13
  // Windows 전용 설치 명령
14
- export const PSMUX_INSTALL_COMMANDS = [
14
+ const PSMUX_INSTALL_COMMANDS = [
15
15
  "winget install psmux",
16
16
  "scoop install psmux",
17
17
  "choco install psmux",
18
18
  "cargo install psmux",
19
19
  ];
20
20
 
21
- export const PSMUX_UPDATE_COMMANDS = [
21
+ const PSMUX_UPDATE_COMMANDS = [
22
22
  "winget upgrade psmux",
23
23
  "scoop update psmux",
24
24
  "choco upgrade psmux",
25
25
  "cargo install psmux --force",
26
26
  ];
27
27
 
28
- export function getPsmuxInstallCommandsFor(platform = process.platform) {
28
+ function getPsmuxInstallCommandsFor(platform = process.platform) {
29
29
  if (platform === "win32") return PSMUX_INSTALL_COMMANDS;
30
30
  return [platform === "darwin" ? "brew install tmux" : "apt install tmux"];
31
31
  }
32
32
 
33
- export function getPsmuxUpdateCommandsFor(platform = process.platform) {
33
+ function getPsmuxUpdateCommandsFor(platform = process.platform) {
34
34
  if (platform === "win32") return PSMUX_UPDATE_COMMANDS;
35
35
  return [
36
36
  platform === "darwin"
@@ -39,7 +39,7 @@ export function getPsmuxUpdateCommandsFor(platform = process.platform) {
39
39
  ];
40
40
  }
41
41
 
42
- export function formatPsmuxCommandList(
42
+ function formatPsmuxCommandList(
43
43
  commands = PSMUX_INSTALL_COMMANDS,
44
44
  indent = "",
45
45
  ) {
@@ -60,12 +60,12 @@ export function formatPsmuxUpdateGuidance(
60
60
  return formatPsmuxCommandList(getPsmuxUpdateCommandsFor(platform), indent);
61
61
  }
62
62
 
63
- export function parsePsmuxVersion(output = "") {
63
+ function parsePsmuxVersion(output = "") {
64
64
  const match = String(output).match(/psmux\s+v?(\d+\.\d+\.\d+)/i);
65
65
  return match?.[1] || null;
66
66
  }
67
67
 
68
- export function compareSemver(a, b) {
68
+ function compareSemver(a, b) {
69
69
  const left = String(a || "")
70
70
  .split(".")
71
71
  .map((part) => Number.parseInt(part, 10) || 0);
@@ -79,7 +79,7 @@ export function compareSemver(a, b) {
79
79
  return 0;
80
80
  }
81
81
 
82
- export function isRecommendedPsmuxVersion(version) {
82
+ function isRecommendedPsmuxVersion(version) {
83
83
  if (!version) return false;
84
84
  return compareSemver(version, PSMUX_RECOMMENDED_VERSION) >= 0;
85
85
  }
@@ -143,51 +143,3 @@ export function probePsmuxSupport(options = {}) {
143
143
  };
144
144
  }
145
145
  }
146
-
147
- export function probePrimaryMultiplexerSupport(options = {}) {
148
- const platform = options.platform || process.platform;
149
- const execFileSyncFn = options.execFileSyncFn || execFileSync;
150
-
151
- if (platform === "win32") {
152
- return {
153
- kind: "psmux",
154
- ...probePsmuxSupport(options),
155
- };
156
- }
157
-
158
- const bin = options.bin || "tmux";
159
- try {
160
- const versionOutput = execFileSyncFn(bin, ["-V"], {
161
- encoding: "utf8",
162
- timeout: 2000,
163
- stdio: ["ignore", "pipe", "pipe"],
164
- windowsHide: true,
165
- });
166
- return {
167
- ok: true,
168
- installed: true,
169
- kind: "tmux",
170
- version: String(versionOutput || "").trim() || null,
171
- recommendedVersion: null,
172
- recommended: true,
173
- missingCommands: [],
174
- missingOptionalCommands: [],
175
- installHint: formatPsmuxInstallGuidance(" ", platform),
176
- updateHint: formatPsmuxUpdateGuidance(" ", platform),
177
- };
178
- } catch (error) {
179
- return {
180
- ok: false,
181
- installed: false,
182
- kind: "tmux",
183
- version: null,
184
- recommendedVersion: null,
185
- recommended: false,
186
- missingCommands: ["tmux"],
187
- missingOptionalCommands: [],
188
- installHint: formatPsmuxInstallGuidance(" ", platform),
189
- updateHint: formatPsmuxUpdateGuidance(" ", platform),
190
- error: error?.message || String(error),
191
- };
192
- }
193
- }
@@ -139,7 +139,7 @@ function commandUsesClaudePrintMode(command) {
139
139
  );
140
140
  }
141
141
 
142
- function shouldSkipInteractiveRegistration(payload, seams = {}) {
142
+ export function shouldSkipInteractiveRegistration(payload, seams = {}) {
143
143
  const declaredKind = String(
144
144
  payload?.sessionKind || payload?.session_kind || "",
145
145
  )