@triflux/core 10.69.0 → 10.71.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.
@@ -10,7 +10,7 @@
10
10
  "critic": "codex",
11
11
  "analyst": "codex",
12
12
  "code-reviewer": "codex",
13
- "security-reviewer": "codex",
13
+ "security-reviewer": "antigravity",
14
14
  "quality-reviewer": "codex",
15
15
  "scientist": "codex",
16
16
  "scientist-deep": "codex",
package/hub/team/pane.mjs CHANGED
@@ -1,8 +1,9 @@
1
1
  // hub/team/pane.mjs — pane별 CLI 실행 + stdin 주입
2
2
  // 의존성: child_process, fs, os, path (Node.js 내장)만 사용
3
- import { unlinkSync, writeFileSync } from "node:fs";
3
+ import { existsSync, unlinkSync, writeFileSync } from "node:fs";
4
4
  import { createRequire } from "node:module";
5
- import { join } from "node:path";
5
+ import { dirname, join } from "node:path";
6
+ import { fileURLToPath } from "node:url";
6
7
  import { privateTmpDir } from "../lib/private-tmp.mjs";
7
8
  import { psmuxExec } from "./psmux.mjs";
8
9
  import { detectMultiplexer, tmuxExec } from "./session.mjs";
@@ -56,7 +57,32 @@ export function resolveCli(cli) {
56
57
  * @param {string} cli: CLI 이름, 별칭(agy) 또는 역할명
57
58
  * @returns {string} 실행할 셸 커맨드
58
59
  */
59
- export function buildCliCommand(cli) {
60
+ export function buildCliCommand(cli, prompt) {
61
+ if (["code-reviewer", "quality-reviewer"].includes(cli)) {
62
+ // 작성 벤더 선택은 라우터에 맡기고 프롬프트가 준비된 뒤 실행한다.
63
+ if (prompt === undefined) return "";
64
+ const routeScript = [
65
+ process.env.TFX_ROUTE_SCRIPT,
66
+ process.env.TFX_DELEGATOR_ROUTE_SCRIPT,
67
+ join(
68
+ dirname(fileURLToPath(import.meta.url)),
69
+ "../../scripts/tfx-route.sh",
70
+ ),
71
+ join(process.env.HOME || "", ".claude/scripts/tfx-route.sh"),
72
+ ].find((path) => path && existsSync(path));
73
+ if (!routeScript) throw new Error("tfx-route.sh 를 찾지 못했다.");
74
+ const quote = (value) => `'${String(value).replace(/'/g, "'\\''")}'`;
75
+ const reviewEnv = ["TFX_REVIEW_AUTHOR", "TFX_CLI_MODE"]
76
+ .filter((key) => process.env[key] !== undefined)
77
+ .map((key) => `${key}=${quote(process.env[key])} `)
78
+ .join("");
79
+ const script = `${reviewEnv}bash ${quote(routeScript.replace(/\\/g, "/"))} ${quote(cli)} ${quote(prompt)} review < /dev/null`;
80
+ const quotedScript =
81
+ process.platform === "win32"
82
+ ? `'${script.replace(/'/g, "''")}'`
83
+ : quote(script);
84
+ return `bash -lc ${quotedScript}`;
85
+ }
60
86
  switch (resolveCli(cli)) {
61
87
  case "codex":
62
88
  return "codex --dangerously-bypass-approvals-and-sandbox";
@@ -78,7 +104,7 @@ export function buildCliCommand(cli) {
78
104
  */
79
105
  export function startCliInPane(target, command) {
80
106
  // CLI 시작도 buffer paste를 재사용해 셸/플랫폼별 quoting 차이를 제거한다.
81
- injectPrompt(target, command);
107
+ if (command) injectPrompt(target, command);
82
108
  }
83
109
 
84
110
  /**
@@ -247,6 +273,11 @@ export function injectPrompt(
247
273
  prompt,
248
274
  { useFileRef = false, cli = null } = {},
249
275
  ) {
276
+ if (["code-reviewer", "quality-reviewer"].includes(cli)) {
277
+ prompt = buildCliCommand(cli, prompt);
278
+ cli = null;
279
+ useFileRef = false;
280
+ }
250
281
  const tmpDir = privateTmpDir("tfx-multi");
251
282
 
252
283
  const safeTarget = target.replace(/[:.]/g, "-");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@triflux/core",
3
- "version": "10.69.0",
3
+ "version": "10.71.0",
4
4
  "description": "triflux core — CLI routing, adapters. Zero native dependencies.",
5
5
  "type": "module",
6
6
  "main": "hub/index.mjs",
@@ -1,6 +1,8 @@
1
1
  // Canonical Codex role policy. Concrete model IDs stay in profile config files.
2
2
 
3
3
  import { readFileSync } from "node:fs";
4
+ import { homedir } from "node:os";
5
+ import { join } from "node:path";
4
6
  import { pathToFileURL } from "node:url";
5
7
  import {
6
8
  normalizeCodexProfileName,
@@ -298,6 +300,7 @@ const NATIVE_CODEX_AGENTS = new Set([
298
300
  "test-engineer",
299
301
  "qa-tester",
300
302
  ]);
303
+ const CLAUDE_REVIEW_USAGE_THRESHOLD = 80;
301
304
 
302
305
  function resolveDirectCodexRoutePolicy(agent) {
303
306
  if (agent === "explore") {
@@ -315,6 +318,7 @@ export function resolveAgentRoute(
315
318
  profileOverride = "auto",
316
319
  nested = false,
317
320
  geminiProfileResolver = resolveGeminiProfileForPurpose,
321
+ { reviewAuthor = "codex", weeklyPercent = 0 } = {},
318
322
  ) {
319
323
  if (!/^[A-Za-z0-9_.-]+$/.test(agent)) {
320
324
  throw new Error(`invalid agent: ${agent}`);
@@ -324,10 +328,27 @@ export function resolveAgentRoute(
324
328
  `알 수 없는 에이전트 타입: ${agent}; available: ${Object.keys(map).join(", ")}`,
325
329
  );
326
330
  }
327
- const provider = map[agent];
331
+ let provider = map[agent];
328
332
  if (!["codex", "antigravity", "claude"].includes(provider)) {
329
333
  throw new Error(`invalid provider for ${agent}: ${provider}`);
330
334
  }
335
+ const byAuthor = agent === "code-reviewer" || agent === "quality-reviewer";
336
+ let usageSwitch = false;
337
+ if (byAuthor) {
338
+ if (!["codex", "claude", "antigravity"].includes(reviewAuthor)) {
339
+ throw new Error(
340
+ `TFX_REVIEW_AUTHOR 값이 유효하지 않습니다: ${reviewAuthor}`,
341
+ );
342
+ }
343
+ usageSwitch =
344
+ reviewAuthor !== "claude" &&
345
+ Number.isFinite(weeklyPercent) &&
346
+ weeklyPercent >= CLAUDE_REVIEW_USAGE_THRESHOLD;
347
+ if (reviewAuthor === "claude") provider = "codex";
348
+ else if (usageSwitch)
349
+ provider = reviewAuthor === "codex" ? "antigravity" : "codex";
350
+ else provider = "claude";
351
+ }
331
352
  const policy = resolveDirectCodexRoutePolicy(agent);
332
353
  const normalizedOverride = normalizeCodexProfileName(profileOverride);
333
354
  let resolvedOverride = policy.profile;
@@ -354,6 +375,8 @@ export function resolveAgentRoute(
354
375
  geminiProfileResolver(agent),
355
376
  EXPECTED_DURATION_BY_AGENT[agent] ?? 30,
356
377
  NATIVE_CODEX_AGENTS.has(agent),
378
+ byAuthor ? reviewAuthor : "",
379
+ usageSwitch,
357
380
  ];
358
381
  }
359
382
 
@@ -402,6 +425,23 @@ async function runCli() {
402
425
  if (format === "route") {
403
426
  if (!mapFile) throw new Error("--map-file is required for route format");
404
427
  const map = JSON.parse(readFileSync(mapFile, "utf8"));
428
+ let weeklyPercent = 0;
429
+ if (agent === "code-reviewer" || agent === "quality-reviewer") {
430
+ try {
431
+ const data = JSON.parse(
432
+ readFileSync(
433
+ join(homedir(), ".claude/cache/claude-usage-cache.json"),
434
+ "utf8",
435
+ ),
436
+ ).data;
437
+ weeklyPercent =
438
+ Date.parse(data?.weeklyResetsAt) <= Date.now()
439
+ ? 0
440
+ : data?.weeklyPercent;
441
+ } catch {
442
+ // 사용량을 읽지 못하면 한도 미만으로 취급한다.
443
+ }
444
+ }
405
445
  let resolver = resolveGeminiProfileForPurpose;
406
446
  if (geminiProfilesModule) {
407
447
  try {
@@ -418,9 +458,10 @@ async function runCli() {
418
458
  }
419
459
  }
420
460
  process.stdout.write(
421
- resolveAgentRoute(agent, map, profileOverride, nested, resolver).join(
422
- "\x1e",
423
- ),
461
+ resolveAgentRoute(agent, map, profileOverride, nested, resolver, {
462
+ reviewAuthor: process.env.TFX_REVIEW_AUTHOR ?? "codex",
463
+ weeklyPercent,
464
+ }).join("\x1e"),
424
465
  );
425
466
  return;
426
467
  }