@triflux/remote 10.68.0 → 10.70.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",
@@ -180,7 +180,18 @@ function buildRouteBackedHeadlessCommand(
180
180
  ? String(Math.trunc(Number(opts.timeoutSec)))
181
181
  : "";
182
182
  const timeoutArg = timeout ? ` ${shellQuote(timeout)}` : "";
183
- const routeMode = resolvedCli === "antigravity" ? "antigravity" : resolvedCli;
183
+ const reviewByAuthor = ["code-reviewer", "quality-reviewer"].includes(
184
+ routeAgent,
185
+ );
186
+ const cliModeEnv = reviewByAuthor
187
+ ? ""
188
+ : `TFX_CLI_MODE=${shellQuote(resolvedCli)} `;
189
+ const reviewEnv = reviewByAuthor
190
+ ? ["TFX_REVIEW_AUTHOR", "TFX_CLI_MODE"]
191
+ .filter((key) => process.env[key] !== undefined)
192
+ .map((key) => `${key}=${shellQuote(process.env[key])} `)
193
+ .join("")
194
+ : "";
184
195
  const codexProfileEnv =
185
196
  resolvedCli === "codex" && opts.codexProfile
186
197
  ? `TFX_CODEX_PROFILE=${shellQuote(opts.codexProfile)} `
@@ -195,7 +206,7 @@ function buildRouteBackedHeadlessCommand(
195
206
  .join("");
196
207
  const script =
197
208
  `__tfx_prompt=$(cat ${shellQuote(promptFile)}); ` +
198
- `${lifecycleEnv}${codexProfileEnv}TFX_CLI_MODE=${shellQuote(routeMode)} ` +
209
+ `${lifecycleEnv}${codexProfileEnv}${cliModeEnv}${reviewEnv}` +
199
210
  `bash ${shellQuote(routeScript)} ${shellQuote(routeAgent)} "$__tfx_prompt" ${shellQuote(mcp)}${timeoutArg} ` +
200
211
  `> ${shellQuote(resultFile)} 2>${shellQuote(`${resultFile}.err`)} < /dev/null`;
201
212
  return `bash -lc ${shellQuote(script)}`;
@@ -218,6 +229,10 @@ const MCP_PROFILE_HINTS = {
218
229
  export function buildHeadlessCommand(cli, prompt, resultFile, opts = {}) {
219
230
  const { handoff = true, mcp, contextFile, model, cwd } = opts;
220
231
  const resolvedCli = resolveCliType(cli);
232
+ const routeAgent = opts.role || cli;
233
+ const reviewByAuthor = ["code-reviewer", "quality-reviewer"].includes(
234
+ routeAgent,
235
+ );
221
236
 
222
237
  // contextFile 처리: 32KB(32768 bytes) 초과 시 UTF-8 안전 절단
223
238
  let contextPrefix = "";
@@ -271,9 +286,10 @@ export function buildHeadlessCommand(cli, prompt, resultFile, opts = {}) {
271
286
  })
272
287
  : undefined;
273
288
  const backendCommand =
274
- resolvedCli === "codex" || resolvedCli === "antigravity"
289
+ reviewByAuthor || resolvedCli === "codex" || resolvedCli === "antigravity"
275
290
  ? buildRouteBackedHeadlessCommand(resolvedCli, promptFile, resultFile, {
276
291
  ...opts,
292
+ role: reviewByAuthor ? routeAgent : opts.role,
277
293
  codexProfile,
278
294
  mcp,
279
295
  })
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 "@triflux/core/hub/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, "-");
@@ -109,6 +109,8 @@ function buildClaudeArgs(worker, options) {
109
109
  args.push("--print");
110
110
  args.push("--input-format", "stream-json");
111
111
  args.push("--output-format", "stream-json");
112
+ // Claude Code 2.1.296 은 --print 와 stream-json 출력을 함께 쓰면 --verbose 를 요구한다.
113
+ args.push("--verbose");
112
114
 
113
115
  if (options.includePartialMessages) args.push("--include-partial-messages");
114
116
  if (options.replayUserMessages) args.push("--replay-user-messages");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@triflux/remote",
3
- "version": "10.68.0",
3
+ "version": "10.70.0",
4
4
  "description": "triflux remote — team mode, psmux, MCP workers.",
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
  }