@triflux/remote 10.91.0 → 10.92.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.
@@ -11,11 +11,15 @@ import {
11
11
  writeFileSync,
12
12
  } from "node:fs";
13
13
  import { createRequire } from "node:module";
14
+ import { homedir } from "node:os";
14
15
  import { delimiter, dirname, isAbsolute, join, resolve } from "node:path";
15
16
  import { fileURLToPath } from "node:url";
16
17
  import { resolveNestedCodexAgentProfile } from "../../scripts/lib/agent-route-policy.mjs";
17
18
  import { escapePwshSingleQuoted } from "@triflux/core/hub/cli-adapter-base.mjs";
18
- import { judgeWorkerHealth } from "@triflux/core/hub/lib/jev-addon.mjs";
19
+ import {
20
+ judgeWorkerHealth,
21
+ recordMcpSelectionShadow,
22
+ } from "@triflux/core/hub/lib/jev-addon.mjs";
19
23
  import { privateTmpDir } from "@triflux/core/hub/lib/private-tmp.mjs";
20
24
  import {
21
25
  createActivityLifecycle,
@@ -227,6 +231,34 @@ const MCP_PROFILE_HINTS = {
227
231
  docs: "Focus on documentation and explanation tasks.",
228
232
  };
229
233
 
234
+ function claudeMcpServerNames(cwd = process.cwd()) {
235
+ const names = new Set();
236
+ for (const file of [
237
+ join(process.env.HOME || homedir(), ".claude.json"),
238
+ join(cwd, ".mcp.json"),
239
+ ]) {
240
+ try {
241
+ const servers = JSON.parse(readFileSync(file, "utf8")).mcpServers;
242
+ for (const name of Object.keys(servers ?? {})) names.add(name);
243
+ } catch {
244
+ // 없는 설정 파일은 후보에서 뺀다.
245
+ }
246
+ }
247
+ return [...names];
248
+ }
249
+
250
+ // 워커 실행과 무관한 기록이라 기다리지 않고, 실패해도 워커에 영향을 주지 않는다.
251
+ function shadowClaudeMcpSelection(assignment) {
252
+ if (assignment.cli !== "claude") return;
253
+ recordMcpSelectionShadow({
254
+ taskText: assignment.prompt,
255
+ agentType: assignment.role || "default",
256
+ availableServers: claudeMcpServerNames(
257
+ assignment.cwd || assignment.workdir,
258
+ ),
259
+ }).catch(() => {});
260
+ }
261
+
230
262
  export function buildHeadlessCommand(cli, prompt, resultFile, opts = {}) {
231
263
  const { handoff = true, mcp, contextFile, model, cwd } = opts;
232
264
  const resolvedCli = resolveCliType(cli);
@@ -599,6 +631,7 @@ async function dispatchProgressive(sessionName, assignments, opts = {}) {
599
631
  const resultFile = headlessResultFile(sessionName, paneName);
600
632
  // Issue #118 review R1 HIGH: stale artifact 제거 (이전 run / restart 잔재)
601
633
  cleanStaleResultArtifacts(resultFile);
634
+ shadowClaudeMcpSelection(assignment);
602
635
  const cmd = buildHeadlessCommand(
603
636
  assignment.cli,
604
637
  assignment.prompt,
@@ -687,6 +720,7 @@ async function dispatchBatch(sessionName, assignments, opts = {}) {
687
720
  const resultFile = headlessResultFile(sessionName, paneName);
688
721
  // Issue #118 review R1 HIGH: stale artifact 제거 (이전 run / restart 잔재)
689
722
  cleanStaleResultArtifacts(resultFile);
723
+ shadowClaudeMcpSelection(assignment);
690
724
  const cmd = buildHeadlessCommand(
691
725
  assignment.cli,
692
726
  assignment.prompt,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@triflux/remote",
3
- "version": "10.91.0",
3
+ "version": "10.92.0",
4
4
  "description": "triflux remote — team mode, psmux, MCP workers.",
5
5
  "type": "module",
6
6
  "main": "hub/index.mjs",
@@ -187,6 +187,15 @@ const CANONICAL_CODEX_PROFILE_OVERRIDES = Object.freeze(
187
187
  "astra_ultra",
188
188
  ]),
189
189
  );
190
+ // 비용 순서다. Jev 는 방향만 답하고 등급은 여기서 한 단계만 옮긴다.
191
+ const CODEX_TIER_LADDER = Object.freeze([
192
+ "luna_low",
193
+ "luna_high",
194
+ "sol_med",
195
+ "sol_high",
196
+ "astra_xhigh",
197
+ ]);
198
+ const NO_LOWER_CODEX_AGENTS = Object.freeze(new Set(["security-reviewer"]));
190
199
  const ULTRA_ELIGIBLE_CODEX_AGENTS = Object.freeze(
191
200
  new Set(["deep-executor", "scientist-deep"]),
192
201
  );
@@ -212,6 +221,16 @@ export function resolveCodexAgentPolicy(agent) {
212
221
  * later process-level `TFX_CODEX_PROFILE`. Ultra is reserved for an eligible
213
222
  * top-level role; every nested/sandboxed or ineligible request is max.
214
223
  */
224
+ export function shiftCodexAgentProfile(agent, tierShift) {
225
+ const { profile } = resolveDirectCodexRoutePolicy(agent);
226
+ const index = CODEX_TIER_LADDER.indexOf(profile);
227
+ if (index === -1) return profile;
228
+ if (tierShift === "raise") return CODEX_TIER_LADDER[index + 1] ?? profile;
229
+ if (tierShift === "lower" && !NO_LOWER_CODEX_AGENTS.has(agent))
230
+ return CODEX_TIER_LADDER[index - 1] ?? profile;
231
+ return profile;
232
+ }
233
+
215
234
  export function resolveCodexAgentProfile(
216
235
  agent,
217
236
  {
@@ -400,6 +419,7 @@ function parseCliArgs(argv) {
400
419
  let profileOverride = "auto";
401
420
  let geminiProfilesModule = "";
402
421
  let nested = false;
422
+ let tierShift = "";
403
423
  while (args.length > 0) {
404
424
  const flag = args.shift();
405
425
  if (flag === "--format") format = args.shift() || "";
@@ -410,6 +430,7 @@ function parseCliArgs(argv) {
410
430
  else if (flag === "--gemini-profiles-module")
411
431
  geminiProfilesModule = args.shift() || "";
412
432
  else if (flag === "--nested") nested = true;
433
+ else if (flag === "--tier-shift") tierShift = args.shift() || "";
413
434
  else throw new Error(`unknown argument: ${flag}`);
414
435
  }
415
436
  if (!new Set(["json", "tsv", "route"]).has(format)) {
@@ -422,6 +443,7 @@ function parseCliArgs(argv) {
422
443
  profileOverride,
423
444
  geminiProfilesModule,
424
445
  nested,
446
+ tierShift,
425
447
  };
426
448
  }
427
449
 
@@ -433,9 +455,14 @@ async function runCli() {
433
455
  profileOverride,
434
456
  geminiProfilesModule,
435
457
  nested,
458
+ tierShift,
436
459
  } = parseCliArgs(process.argv.slice(2));
437
460
  if (format === "route") {
438
461
  if (!mapFile) throw new Error("--map-file is required for route format");
462
+ const routedProfile =
463
+ tierShift && normalizeCodexProfileName(profileOverride) === "auto"
464
+ ? shiftCodexAgentProfile(agent, tierShift)
465
+ : profileOverride;
439
466
  const map = JSON.parse(readFileSync(mapFile, "utf8"));
440
467
  let weeklyPercent = 0;
441
468
  if (agent === "code-reviewer" || agent === "quality-reviewer") {
@@ -470,7 +497,7 @@ async function runCli() {
470
497
  }
471
498
  }
472
499
  process.stdout.write(
473
- resolveAgentRoute(agent, map, profileOverride, nested, resolver, {
500
+ resolveAgentRoute(agent, map, routedProfile, nested, resolver, {
474
501
  reviewAuthor: process.env.TFX_REVIEW_AUTHOR ?? "codex",
475
502
  weeklyPercent,
476
503
  }).join("\x1e"),