@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.
- package/hooks/agy-session-hook.mjs +11 -56
- package/hooks/codex-session-hook.mjs +11 -101
- package/hub/bridge.mjs +1 -79
- package/hub/cli-adapter-base.mjs +12 -90
- package/hub/codex-adapter.mjs +0 -1
- package/hub/codex-preflight.mjs +5 -28
- package/hub/index.mjs +1 -1
- package/hub/lib/known-errors.json +2 -2
- package/hub/lib/process-utils.mjs +0 -123
- package/hub/lib/spawn-trace.mjs +3 -2
- package/hub/lib/timeout-defaults.mjs +0 -1
- package/hub/role-contract.mjs +0 -2
- package/hud/cli-policy.mjs +0 -22
- package/hud/constants.mjs +11 -26
- package/hud/hud-qos-status.mjs +23 -51
- package/hud/providers/gemini.mjs +105 -20
- package/hud/renderers.mjs +50 -104
- package/hud/utils.mjs +2 -2
- package/package.json +1 -3
- package/scripts/lib/agent-route-policy.mjs +162 -14
- package/scripts/lib/claudemd-scanner.mjs +2 -199
- package/scripts/lib/cli-claude.mjs +0 -2
- package/scripts/lib/context.mjs +3 -19
- package/scripts/lib/env-probe.mjs +5 -64
- package/scripts/lib/gemini-profiles.mjs +2 -69
- package/scripts/lib/machine-profile.mjs +3 -17
- package/scripts/lib/mcp-filter.mjs +109 -53
- package/scripts/lib/mcp-health.mjs +2 -2
- package/scripts/lib/mcp-server-catalog.mjs +2 -2
- package/scripts/lib/psmux-info.mjs +11 -59
- package/scripts/lib/session-presence.mjs +1 -1
- package/scripts/lib/skill-template.mjs +0 -245
- package/hub/dynamic-routing-engine.mjs +0 -511
- package/hub/lib/risk-tier.mjs +0 -53
- package/hub/lib/ssh-command.mjs +0 -231
- package/hub/lib/staged-file-detect.mjs +0 -43
- package/hub/lib/tfx-route-args.mjs +0 -353
- package/hub/routing-snapshot.mjs +0 -261
- package/mesh/index.mjs +0 -69
- package/mesh/mesh-budget.mjs +0 -137
- package/mesh/mesh-heartbeat.mjs +0 -100
- package/mesh/mesh-protocol.mjs +0 -96
- package/mesh/mesh-queue.mjs +0 -163
- package/mesh/mesh-registry.mjs +0 -78
- package/mesh/mesh-router.mjs +0 -77
- package/scripts/lib/dynamic-route-cli.mjs +0 -107
package/hud/renderers.mjs
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
// ============================================================================
|
|
2
2
|
// 라인 렌더러 (tier별 행 생성)
|
|
3
3
|
// ============================================================================
|
|
4
|
-
import { readdirSync, statSync } from "node:fs";
|
|
5
|
-
import { join } from "node:path";
|
|
6
4
|
import {
|
|
7
5
|
bold,
|
|
8
6
|
CLAUDE_ORANGE,
|
|
@@ -12,17 +10,18 @@ import {
|
|
|
12
10
|
colorByPercent,
|
|
13
11
|
colorByProvider,
|
|
14
12
|
dim,
|
|
13
|
+
GAUGE_WIDTH,
|
|
14
|
+
GEMINI_BLUE,
|
|
15
15
|
geminiBlue,
|
|
16
|
-
green,
|
|
17
|
-
red,
|
|
18
16
|
yellow,
|
|
19
17
|
} from "./colors.mjs";
|
|
20
18
|
import {
|
|
21
19
|
ACCOUNT_LABEL_WIDTH,
|
|
22
20
|
FIVE_HOUR_MS,
|
|
21
|
+
ONE_DAY_MS,
|
|
22
|
+
PERCENT_CELL_WIDTH,
|
|
23
23
|
PROVIDER_PREFIX_WIDTH,
|
|
24
24
|
SEVEN_DAY_MS,
|
|
25
|
-
TEAM_STATE_DIR,
|
|
26
25
|
} from "./constants.mjs";
|
|
27
26
|
import { buildContextUsageView } from "./context-monitor.mjs";
|
|
28
27
|
import { getTerminalColumns, tierBar, tierDimBar } from "./terminal.mjs";
|
|
@@ -36,94 +35,10 @@ import {
|
|
|
36
35
|
formatTimeCell,
|
|
37
36
|
formatTimeCellDH,
|
|
38
37
|
padAnsiRight,
|
|
39
|
-
readJson,
|
|
40
38
|
stripAnsi,
|
|
41
39
|
truncateAnsi,
|
|
42
40
|
} from "./utils.mjs";
|
|
43
41
|
|
|
44
|
-
// ============================================================================
|
|
45
|
-
// tfx-multi 상태 행 생성 (v2.2 HUD 통합)
|
|
46
|
-
// ============================================================================
|
|
47
|
-
export function getTeamRow(currentTier, stateDir = TEAM_STATE_DIR) {
|
|
48
|
-
let latestPath = null;
|
|
49
|
-
let latestMtime = Date.now() - 24 * 60 * 60 * 1000;
|
|
50
|
-
try {
|
|
51
|
-
for (const name of readdirSync(stateDir)) {
|
|
52
|
-
if (!/^team-state-.+\.json$/.test(name)) continue;
|
|
53
|
-
const path = join(stateDir, name);
|
|
54
|
-
try {
|
|
55
|
-
const stat = statSync(path);
|
|
56
|
-
if (stat.isFile() && stat.mtimeMs > latestMtime) {
|
|
57
|
-
latestPath = path;
|
|
58
|
-
latestMtime = stat.mtimeMs;
|
|
59
|
-
}
|
|
60
|
-
} catch {
|
|
61
|
-
// 세션 종료 중 사라진 파일은 제외한다.
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
} catch {
|
|
65
|
-
return null;
|
|
66
|
-
}
|
|
67
|
-
const teamState = latestPath ? readJson(latestPath, null) : null;
|
|
68
|
-
if (!teamState?.sessionName) return null;
|
|
69
|
-
|
|
70
|
-
const workers = (teamState.members || []).filter((m) => m.role === "worker");
|
|
71
|
-
if (!workers.length) return null;
|
|
72
|
-
|
|
73
|
-
const tasks = teamState.tasks || [];
|
|
74
|
-
const completed = tasks.filter((t) => t.status === "completed").length;
|
|
75
|
-
const failed = tasks.filter((t) => t.status === "failed").length;
|
|
76
|
-
const total = tasks.length || workers.length;
|
|
77
|
-
|
|
78
|
-
// 경과 시간 (80col 이상에서만 표시)
|
|
79
|
-
const elapsed =
|
|
80
|
-
teamState.startedAt && (currentTier === "full" || currentTier === "compact")
|
|
81
|
-
? `${Math.round((Date.now() - teamState.startedAt) / 60000)}m`
|
|
82
|
-
: "";
|
|
83
|
-
|
|
84
|
-
// CLI 브랜드: 단일문자 + ANSI 색상 (x=codex, g=gemini, c=claude)
|
|
85
|
-
const cliTag = (cli) =>
|
|
86
|
-
cli === "codex"
|
|
87
|
-
? bold(codexWhite("x"))
|
|
88
|
-
: cli === "gemini"
|
|
89
|
-
? bold(geminiBlue("g"))
|
|
90
|
-
: bold(claudeOrange("c"));
|
|
91
|
-
// 멤버 상태: 태그 + 상태기호 (60col 이상)
|
|
92
|
-
const memberIcons =
|
|
93
|
-
currentTier === "full" ||
|
|
94
|
-
currentTier === "compact" ||
|
|
95
|
-
currentTier === "minimal"
|
|
96
|
-
? workers
|
|
97
|
-
.map((m) => {
|
|
98
|
-
const task = tasks.find((t) => t.owner === m.name);
|
|
99
|
-
const status =
|
|
100
|
-
task?.status === "completed"
|
|
101
|
-
? green("\u2713")
|
|
102
|
-
: task?.status === "in_progress"
|
|
103
|
-
? yellow("\u22EF")
|
|
104
|
-
: task?.status === "failed"
|
|
105
|
-
? red("\u2717")
|
|
106
|
-
: dim("\u25CC");
|
|
107
|
-
return `${cliTag(m.cli)}${status}`;
|
|
108
|
-
})
|
|
109
|
-
.join(" ")
|
|
110
|
-
: "";
|
|
111
|
-
|
|
112
|
-
// 진행 텍스트
|
|
113
|
-
const doneText =
|
|
114
|
-
failed > 0
|
|
115
|
-
? `${completed}/${total} ${red(`${failed}\u2717`)}`
|
|
116
|
-
: `${completed}/${total}`;
|
|
117
|
-
|
|
118
|
-
const leftText = elapsed ? `${doneText} ${dim(elapsed)}` : doneText;
|
|
119
|
-
|
|
120
|
-
return {
|
|
121
|
-
prefix: bold(claudeOrange("\u25B2")),
|
|
122
|
-
left: leftText,
|
|
123
|
-
right: memberIcons,
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
|
|
127
42
|
// ============================================================================
|
|
128
43
|
// 행 정렬 렌더링
|
|
129
44
|
// ============================================================================
|
|
@@ -142,9 +57,7 @@ export function renderAlignedRows(rows) {
|
|
|
142
57
|
if (!hasRight) {
|
|
143
58
|
return truncateAnsi(`${prefix} ${row.left}`, cols);
|
|
144
59
|
}
|
|
145
|
-
|
|
146
|
-
const ownLen = stripAnsi(row.left).length;
|
|
147
|
-
const effectiveWidth = Math.min(rawLeftWidth, ownLen + 2);
|
|
60
|
+
const effectiveWidth = rawLeftWidth;
|
|
148
61
|
const left = padAnsiRight(row.left, effectiveWidth);
|
|
149
62
|
// 우선순위 기반 truncate: right 먼저 축소, 그래도 넘치면 left 축소
|
|
150
63
|
// prefix(PROVIDER_PREFIX_WIDTH) + " "(1) + left(effectiveWidth) + " | "(3) + right
|
|
@@ -169,10 +82,9 @@ export function getMicroLine(
|
|
|
169
82
|
contextView,
|
|
170
83
|
claudeUsage,
|
|
171
84
|
codexBuckets,
|
|
172
|
-
antigravityReady,
|
|
173
85
|
options = {},
|
|
174
86
|
) {
|
|
175
|
-
const { showCodex = true,
|
|
87
|
+
const { showCodex = true, showAntigravity = true } = options;
|
|
176
88
|
const ctxView = contextView || buildContextUsageView({});
|
|
177
89
|
// Claude 5h/1w
|
|
178
90
|
const cF =
|
|
@@ -211,15 +123,26 @@ export function getMicroLine(
|
|
|
211
123
|
if (showCodex) {
|
|
212
124
|
segments.push(`${bold(codexWhite("x"))}${dim(":")}${xVal}`);
|
|
213
125
|
}
|
|
214
|
-
if (
|
|
126
|
+
if (showAntigravity) {
|
|
127
|
+
const marker = options.antigravityQuota ? bold(geminiBlue("a")) : dim("a");
|
|
215
128
|
segments.push(
|
|
216
|
-
`${
|
|
129
|
+
`${marker}${dim(":")}${antigravityPercentText(options.antigravityQuota)}${options.antigravityQuota?.stale ? dim("*") : ""}`,
|
|
217
130
|
);
|
|
218
131
|
}
|
|
219
132
|
segments.push(`${dim("CTX:")}${contextPercentText(ctxView)}`);
|
|
220
133
|
return truncateAnsi(segments.join(" "), cols);
|
|
221
134
|
}
|
|
222
135
|
|
|
136
|
+
function antigravityPercentText(quota) {
|
|
137
|
+
return quota?.usedPercent != null
|
|
138
|
+
? colorByProvider(
|
|
139
|
+
quota.usedPercent,
|
|
140
|
+
formatPercentCell(quota.usedPercent),
|
|
141
|
+
geminiBlue,
|
|
142
|
+
)
|
|
143
|
+
: dim("--".padStart(PERCENT_CELL_WIDTH));
|
|
144
|
+
}
|
|
145
|
+
|
|
223
146
|
// context 는 토큰 수 대신 사용률만 보여 준다.
|
|
224
147
|
function contextPercentText(ctxView) {
|
|
225
148
|
const text = ctxView.display === "--" ? "--" : `${ctxView.percent}%`;
|
|
@@ -324,7 +247,10 @@ export function getAccountLabel(
|
|
|
324
247
|
const providerState = accountsState?.providers?.[provider] || {};
|
|
325
248
|
const lastId = providerState.last_selected_id;
|
|
326
249
|
const picked = providerConfig.find((a) => a.id === lastId) ||
|
|
327
|
-
providerConfig[0] || {
|
|
250
|
+
providerConfig[0] || {
|
|
251
|
+
id: `${provider}-main`,
|
|
252
|
+
label: provider === "antigravity" ? "agy" : provider,
|
|
253
|
+
};
|
|
328
254
|
let label = picked.label || picked.id;
|
|
329
255
|
if (codexEmail) label = codexEmail;
|
|
330
256
|
if (label.includes("@")) label = label.split("@")[0];
|
|
@@ -347,14 +273,34 @@ export function getProviderRow(
|
|
|
347
273
|
);
|
|
348
274
|
|
|
349
275
|
const prefix = `${bold(markerColor(marker))}:`;
|
|
350
|
-
if (provider === "antigravity"
|
|
276
|
+
if (provider === "antigravity") {
|
|
277
|
+
const usedPercent = realQuota?.usedPercent;
|
|
278
|
+
const active = usedPercent != null;
|
|
279
|
+
const quotaPercent = antigravityPercentText(realQuota);
|
|
280
|
+
const right = `${active ? markerColor(accountLabel) : dim(accountLabel)}${realQuota?.stale ? dim(" [stale]") : ""}`;
|
|
281
|
+
if (currentTier === "nano" || currentTier === "micro") {
|
|
282
|
+
return {
|
|
283
|
+
prefix: active ? prefix : dim(`${marker}:`),
|
|
284
|
+
left: quotaPercent,
|
|
285
|
+
right,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
const bar = active
|
|
289
|
+
? tierBar(currentTier, usedPercent, GEMINI_BLUE)
|
|
290
|
+
: currentTier === "full"
|
|
291
|
+
? " ".repeat(GAUGE_WIDTH + 1)
|
|
292
|
+
: "";
|
|
293
|
+
const reset =
|
|
294
|
+
(Date.parse(realQuota?.resetTime) - Date.now() >= ONE_DAY_MS
|
|
295
|
+
? formatResetRemainingDayHour(realQuota.resetTime)
|
|
296
|
+
: formatResetRemaining(realQuota?.resetTime)) || "n/a";
|
|
297
|
+
const showTime = currentTier === "full" || currentTier === "compact";
|
|
298
|
+
// /usage의 모델별 값에는 5h/1w 식별자가 없으므로 창을 추측하지 않는다.
|
|
299
|
+
const slot = `${dim("--:")}${bar}${quotaPercent}${showTime ? ` ${dim(formatTimeCell(reset))}` : ""}`;
|
|
351
300
|
return {
|
|
352
|
-
prefix,
|
|
353
|
-
left:
|
|
354
|
-
|
|
355
|
-
? markerColor(realQuota?.currentAbbrev || "agy")
|
|
356
|
-
: dim("--"),
|
|
357
|
-
right: accountLabel ? markerColor(accountLabel) : "",
|
|
301
|
+
prefix: active ? prefix : dim(`${marker}:`),
|
|
302
|
+
left: `${slot} ${" ".repeat(stripAnsi(slot).length)}`,
|
|
303
|
+
right,
|
|
358
304
|
};
|
|
359
305
|
}
|
|
360
306
|
const provAnsi = CODEX_WHITE;
|
package/hud/utils.mjs
CHANGED
|
@@ -164,10 +164,10 @@ export function formatTimeCell(value) {
|
|
|
164
164
|
return `(${text.padStart(TIME_CELL_INNER_WIDTH, padChar)})`;
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
-
//
|
|
167
|
+
// n/a도 정상 주간 시간과 같은 너비를 유지한다.
|
|
168
168
|
export function formatTimeCellDH(value) {
|
|
169
169
|
const text = normalizeTimeToken(value);
|
|
170
|
-
return `(${text})`;
|
|
170
|
+
return `(${text.padStart(TIME_CELL_INNER_WIDTH, " ")})`;
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
export function getCliArgValue(flag) {
|
package/package.json
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@triflux/core",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.51.0",
|
|
4
4
|
"description": "triflux core — CLI routing, pipeline, adapters. Zero native dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "hub/index.mjs",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./hub/index.mjs",
|
|
9
9
|
"./hub/*": "./hub/*",
|
|
10
|
-
"./mesh/*": "./mesh/*",
|
|
11
10
|
"./scripts/*": "./scripts/*"
|
|
12
11
|
},
|
|
13
12
|
"engines": {
|
|
@@ -21,7 +20,6 @@
|
|
|
21
20
|
},
|
|
22
21
|
"files": [
|
|
23
22
|
"hub",
|
|
24
|
-
"mesh",
|
|
25
23
|
"scripts/lib",
|
|
26
24
|
"hooks",
|
|
27
25
|
"hud"
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
// Canonical Codex role policy. Concrete model IDs stay in profile config files.
|
|
2
2
|
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
3
5
|
import {
|
|
4
6
|
normalizeCodexProfileName,
|
|
5
7
|
resolveCodexProfileConfigValues,
|
|
6
8
|
} from "./codex-profile-config.mjs";
|
|
9
|
+
import { resolveGeminiProfileForPurpose } from "./gemini-profiles.mjs";
|
|
7
10
|
|
|
8
11
|
const policy = {
|
|
9
12
|
executor: {
|
|
@@ -198,7 +201,7 @@ function freezePolicy(entry) {
|
|
|
198
201
|
return Object.freeze({ ...entry });
|
|
199
202
|
}
|
|
200
203
|
|
|
201
|
-
|
|
204
|
+
const CODEX_AGENT_POLICY = Object.freeze(
|
|
202
205
|
Object.fromEntries(
|
|
203
206
|
Object.entries(policy).map(([agent, entry]) => [
|
|
204
207
|
agent,
|
|
@@ -206,9 +209,9 @@ export const CODEX_AGENT_POLICY = Object.freeze(
|
|
|
206
209
|
]),
|
|
207
210
|
),
|
|
208
211
|
);
|
|
209
|
-
|
|
212
|
+
const DEFAULT_CODEX_AGENT = "executor";
|
|
210
213
|
|
|
211
|
-
|
|
214
|
+
const CANONICAL_CODEX_PROFILE_OVERRIDES = Object.freeze(
|
|
212
215
|
new Set([
|
|
213
216
|
"gpt6_luna_low",
|
|
214
217
|
"gpt6_luna_high",
|
|
@@ -219,7 +222,7 @@ export const CANONICAL_CODEX_PROFILE_OVERRIDES = Object.freeze(
|
|
|
219
222
|
"gpt6_astra_ultra",
|
|
220
223
|
]),
|
|
221
224
|
);
|
|
222
|
-
|
|
225
|
+
const ULTRA_ELIGIBLE_CODEX_AGENTS = Object.freeze(
|
|
223
226
|
new Set(["deep-executor", "scientist-deep"]),
|
|
224
227
|
);
|
|
225
228
|
|
|
@@ -316,31 +319,176 @@ export function resolveNestedCodexAgentProfile(
|
|
|
316
319
|
return effort.toLowerCase() === "ultra" ? "gpt6_astra_max" : candidate;
|
|
317
320
|
}
|
|
318
321
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
322
|
+
const MIN_TIMEOUT_BY_AGENT = Object.freeze({
|
|
323
|
+
"deep-executor": 900,
|
|
324
|
+
architect: 900,
|
|
325
|
+
planner: 900,
|
|
326
|
+
critic: 900,
|
|
327
|
+
analyst: 900,
|
|
328
|
+
"document-specialist": 900,
|
|
329
|
+
scientist: 900,
|
|
330
|
+
"scientist-deep": 900,
|
|
331
|
+
"code-reviewer": 600,
|
|
332
|
+
"security-reviewer": 600,
|
|
333
|
+
"quality-reviewer": 600,
|
|
334
|
+
executor: 300,
|
|
335
|
+
debugger: 300,
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
const EXPECTED_DURATION_BY_AGENT = Object.freeze({
|
|
339
|
+
writer: 90,
|
|
340
|
+
verifier: 90,
|
|
341
|
+
"qa-tester": 90,
|
|
342
|
+
executor: 300,
|
|
343
|
+
debugger: 300,
|
|
344
|
+
"test-engineer": 300,
|
|
345
|
+
"code-reviewer": 600,
|
|
346
|
+
"security-reviewer": 600,
|
|
347
|
+
architect: 600,
|
|
348
|
+
planner: 600,
|
|
349
|
+
critic: 600,
|
|
350
|
+
analyst: 600,
|
|
351
|
+
scientist: 900,
|
|
352
|
+
"scientist-deep": 900,
|
|
353
|
+
"deep-executor": 900,
|
|
354
|
+
"document-specialist": 900,
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
const NATIVE_CODEX_AGENTS = new Set([
|
|
358
|
+
"explore",
|
|
359
|
+
"verifier",
|
|
360
|
+
"test-engineer",
|
|
361
|
+
"qa-tester",
|
|
362
|
+
]);
|
|
363
|
+
|
|
364
|
+
function resolveDirectCodexRoutePolicy(agent) {
|
|
365
|
+
if (agent === "explore") {
|
|
366
|
+
return { ...CODEX_AGENT_POLICY.spark, timeoutSec: 600, mcpHint: "analyze" };
|
|
367
|
+
}
|
|
368
|
+
if (agent === "antigravity" || agent === "agy") {
|
|
369
|
+
return CODEX_AGENT_POLICY.designer;
|
|
370
|
+
}
|
|
371
|
+
return resolveCodexAgentPolicy(agent);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export function resolveAgentRoute(
|
|
375
|
+
agent,
|
|
376
|
+
map,
|
|
377
|
+
profileOverride = "auto",
|
|
378
|
+
nested = false,
|
|
379
|
+
geminiProfileResolver = resolveGeminiProfileForPurpose,
|
|
380
|
+
) {
|
|
381
|
+
if (!/^[A-Za-z0-9_.-]+$/.test(agent)) {
|
|
382
|
+
throw new Error(`invalid agent: ${agent}`);
|
|
383
|
+
}
|
|
384
|
+
if (!Object.hasOwn(map, agent)) {
|
|
385
|
+
throw new Error(
|
|
386
|
+
`알 수 없는 에이전트 타입: ${agent}; available: ${Object.keys(map).join(", ")}`,
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
const provider = map[agent];
|
|
390
|
+
if (!["codex", "antigravity", "claude"].includes(provider)) {
|
|
391
|
+
throw new Error(`invalid provider for ${agent}: ${provider}`);
|
|
392
|
+
}
|
|
393
|
+
const policy = resolveDirectCodexRoutePolicy(agent);
|
|
394
|
+
const normalizedOverride = normalizeCodexProfileName(profileOverride);
|
|
395
|
+
let resolvedOverride = policy.profile;
|
|
396
|
+
if (normalizedOverride !== "auto") {
|
|
397
|
+
try {
|
|
398
|
+
resolvedOverride = resolveCodexAgentProfile(agent, {
|
|
399
|
+
profileOverride: normalizedOverride,
|
|
400
|
+
nested,
|
|
401
|
+
});
|
|
402
|
+
} catch {
|
|
403
|
+
throw new Error(
|
|
404
|
+
`TFX_CODEX_PROFILE 값이 유효하지 않습니다: ${profileOverride}`,
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return [
|
|
409
|
+
provider,
|
|
410
|
+
policy.profile,
|
|
411
|
+
policy.timeoutSec,
|
|
412
|
+
policy.runMode,
|
|
413
|
+
policy.opusOversight,
|
|
414
|
+
policy.mcpHint,
|
|
415
|
+
policy.subcommand,
|
|
416
|
+
normalizedOverride,
|
|
417
|
+
resolvedOverride,
|
|
418
|
+
geminiProfileResolver(agent),
|
|
419
|
+
MIN_TIMEOUT_BY_AGENT[agent] ?? 120,
|
|
420
|
+
EXPECTED_DURATION_BY_AGENT[agent] ?? 30,
|
|
421
|
+
NATIVE_CODEX_AGENTS.has(agent),
|
|
422
|
+
];
|
|
324
423
|
}
|
|
325
424
|
|
|
326
425
|
function parseCliArgs(argv) {
|
|
327
426
|
const args = [...argv];
|
|
328
427
|
let format = "json";
|
|
329
428
|
let agent = DEFAULT_CODEX_AGENT;
|
|
429
|
+
let mapFile = "";
|
|
430
|
+
let profileOverride = "auto";
|
|
431
|
+
let geminiProfilesModule = "";
|
|
432
|
+
let nested = false;
|
|
330
433
|
while (args.length > 0) {
|
|
331
434
|
const flag = args.shift();
|
|
332
435
|
if (flag === "--format") format = args.shift() || "";
|
|
333
436
|
else if (flag === "--agent") agent = args.shift() || "";
|
|
437
|
+
else if (flag === "--map-file") mapFile = args.shift() || "";
|
|
438
|
+
else if (flag === "--profile-override")
|
|
439
|
+
profileOverride = args.shift() || "";
|
|
440
|
+
else if (flag === "--gemini-profiles-module")
|
|
441
|
+
geminiProfilesModule = args.shift() || "";
|
|
442
|
+
else if (flag === "--nested") nested = true;
|
|
334
443
|
else throw new Error(`unknown argument: ${flag}`);
|
|
335
444
|
}
|
|
336
|
-
if (!new Set(["json", "tsv"]).has(format)) {
|
|
445
|
+
if (!new Set(["json", "tsv", "route"]).has(format)) {
|
|
337
446
|
throw new Error(`unsupported format: ${format}`);
|
|
338
447
|
}
|
|
339
|
-
return {
|
|
448
|
+
return {
|
|
449
|
+
format,
|
|
450
|
+
agent,
|
|
451
|
+
mapFile,
|
|
452
|
+
profileOverride,
|
|
453
|
+
geminiProfilesModule,
|
|
454
|
+
nested,
|
|
455
|
+
};
|
|
340
456
|
}
|
|
341
457
|
|
|
342
|
-
function runCli() {
|
|
343
|
-
const {
|
|
458
|
+
async function runCli() {
|
|
459
|
+
const {
|
|
460
|
+
format,
|
|
461
|
+
agent,
|
|
462
|
+
mapFile,
|
|
463
|
+
profileOverride,
|
|
464
|
+
geminiProfilesModule,
|
|
465
|
+
nested,
|
|
466
|
+
} = parseCliArgs(process.argv.slice(2));
|
|
467
|
+
if (format === "route") {
|
|
468
|
+
if (!mapFile) throw new Error("--map-file is required for route format");
|
|
469
|
+
const map = JSON.parse(readFileSync(mapFile, "utf8"));
|
|
470
|
+
let resolver = resolveGeminiProfileForPurpose;
|
|
471
|
+
if (geminiProfilesModule) {
|
|
472
|
+
try {
|
|
473
|
+
const module = await import(pathToFileURL(geminiProfilesModule).href);
|
|
474
|
+
resolver = (agent) => {
|
|
475
|
+
try {
|
|
476
|
+
return module.resolveGeminiProfileForPurpose(agent) || "flash38";
|
|
477
|
+
} catch {
|
|
478
|
+
return "flash38";
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
} catch {
|
|
482
|
+
resolver = () => "flash38";
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
process.stdout.write(
|
|
486
|
+
resolveAgentRoute(agent, map, profileOverride, nested, resolver).join(
|
|
487
|
+
"\x1e",
|
|
488
|
+
),
|
|
489
|
+
);
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
344
492
|
const resolved = resolveCodexAgentPolicy(agent);
|
|
345
493
|
if (format === "tsv") {
|
|
346
494
|
process.stdout.write(
|
|
@@ -360,7 +508,7 @@ function runCli() {
|
|
|
360
508
|
|
|
361
509
|
if (import.meta.url === new URL(process.argv[1], "file:").href) {
|
|
362
510
|
try {
|
|
363
|
-
runCli();
|
|
511
|
+
await runCli();
|
|
364
512
|
} catch (error) {
|
|
365
513
|
process.stderr.write(`[agent-route-policy] ${error.message}\n`);
|
|
366
514
|
process.exitCode = 1;
|