@triflux/core 10.45.0 → 10.47.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/session-start-fast.mjs +6 -0
- package/hub/dynamic-routing-engine.mjs +1 -1
- package/hub/lib/cto-env.mjs +2 -3
- package/hub/team/retry-state-machine.mjs +9 -1
- package/hud/providers/codex.mjs +91 -8
- package/hud/providers/cto.mjs +3 -0
- package/package.json +1 -1
- package/scripts/lib/agent-route-policy.mjs +18 -17
- package/scripts/lib/codex-profile-config.mjs +19 -5
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import { execFile, execFileSync } from "node:child_process";
|
|
14
14
|
import { dirname, join } from "node:path";
|
|
15
15
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
16
|
+
import { resolveRoleControlSnapshot } from "../hub/lib/cto-env.mjs";
|
|
16
17
|
import {
|
|
17
18
|
buildSynapseTaskSummary,
|
|
18
19
|
drainPendingSynapse,
|
|
@@ -95,6 +96,11 @@ async function defaultResolveParticipantLakeRoot(cwd) {
|
|
|
95
96
|
*/
|
|
96
97
|
export async function emitParticipantSessionStarted(stdinData, seams = {}) {
|
|
97
98
|
try {
|
|
99
|
+
if (
|
|
100
|
+
!resolveRoleControlSnapshot(seams.env || process.env).auto_collect_enabled
|
|
101
|
+
) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
98
104
|
const payload = parseStartPayload(stdinData);
|
|
99
105
|
const sessionId = String(payload?.session_id || "").trim();
|
|
100
106
|
if (!sessionId) return null;
|
package/hub/lib/cto-env.mjs
CHANGED
|
@@ -38,10 +38,9 @@ export function resolveRoleControlSnapshot(env = process.env, options = {}) {
|
|
|
38
38
|
const ctoManagerEnabled = masterEnabled && isEnvOn(env, "TFX_CTO_MANAGER");
|
|
39
39
|
const leadManagerEnabled =
|
|
40
40
|
ctoManagerEnabled && isEnvOn(env, "TFX_LEAD_MANAGER");
|
|
41
|
-
const northStarEnabled =
|
|
42
|
-
masterEnabled && !isEnvOff(env, "TFX_CTO_NORTH_STAR");
|
|
41
|
+
const northStarEnabled = masterEnabled && isEnvOn(env, "TFX_CTO_NORTH_STAR");
|
|
43
42
|
const autoCollectEnabled =
|
|
44
|
-
masterEnabled &&
|
|
43
|
+
masterEnabled && isEnvOn(env, "TFX_CTO_AUTO_COLLECT");
|
|
45
44
|
const values = {
|
|
46
45
|
master_enabled: masterEnabled,
|
|
47
46
|
cto_manager_enabled: ctoManagerEnabled,
|
|
@@ -23,7 +23,10 @@ import {
|
|
|
23
23
|
} from "node:fs";
|
|
24
24
|
import { dirname, join } from "node:path";
|
|
25
25
|
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
canonicalCodexProfileModel,
|
|
28
|
+
normalizeCodexProfileName,
|
|
29
|
+
} from "../../scripts/lib/codex-profile-config.mjs";
|
|
27
30
|
|
|
28
31
|
export const STATES = Object.freeze({
|
|
29
32
|
PLANNING: "PLANNING",
|
|
@@ -318,6 +321,11 @@ function normalizeEscalationChainEntry(entry, index, source) {
|
|
|
318
321
|
const profile = entry.profile.trim();
|
|
319
322
|
normalized.profile =
|
|
320
323
|
entry.cli === "codex" ? normalizeEscalationProfile(profile) : profile;
|
|
324
|
+
// A remapped legacy profile must not keep reporting its old model.
|
|
325
|
+
if (normalized.profile !== profile) {
|
|
326
|
+
normalized.model =
|
|
327
|
+
canonicalCodexProfileModel(normalized.profile) ?? normalized.model;
|
|
328
|
+
}
|
|
321
329
|
}
|
|
322
330
|
return normalized;
|
|
323
331
|
}
|
package/hud/providers/codex.mjs
CHANGED
|
@@ -38,6 +38,8 @@ export function classifyBucket(bucket) {
|
|
|
38
38
|
|
|
39
39
|
// primary/secondary를 window_minutes 기준으로 정규화
|
|
40
40
|
// HUD 규약: primary=5h, secondary=1w
|
|
41
|
+
// window_minutes 가 비어 있으면 길이를 모르므로 보고된 칸에 둔다(app-server 스키마에서
|
|
42
|
+
// windowDurationMins 는 선택 필드). 길이를 알지만 5h/1w 가 아닌 버킷은 버린다.
|
|
41
43
|
export function normalizeBuckets(rl) {
|
|
42
44
|
let primary = null;
|
|
43
45
|
let secondary = null;
|
|
@@ -46,6 +48,12 @@ export function normalizeBuckets(rl) {
|
|
|
46
48
|
if (kind === "five_hour") primary = bucket;
|
|
47
49
|
else if (kind === "weekly") secondary = bucket;
|
|
48
50
|
}
|
|
51
|
+
if (!primary && rl.primary && rl.primary.window_minutes == null) {
|
|
52
|
+
primary = rl.primary;
|
|
53
|
+
}
|
|
54
|
+
if (!secondary && rl.secondary && rl.secondary.window_minutes == null) {
|
|
55
|
+
secondary = rl.secondary;
|
|
56
|
+
}
|
|
49
57
|
return { primary, secondary };
|
|
50
58
|
}
|
|
51
59
|
|
|
@@ -104,7 +112,35 @@ function isNewerSnapshot(candidate, current) {
|
|
|
104
112
|
);
|
|
105
113
|
}
|
|
106
114
|
|
|
107
|
-
function
|
|
115
|
+
function positiveWindowMinutes(bucket) {
|
|
116
|
+
const minutes = Number(bucket?.window_minutes);
|
|
117
|
+
return minutes > 0 ? minutes : null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 계정 하나에는 같은 종류의 창이 한 번에 하나만 열린다. 브로커 계정이 없으면
|
|
121
|
+
// ~/.codex/sessions 와 HUD 라벨이 모두 ~/.codex/auth.json 계정 하나의 것이다. 그래서
|
|
122
|
+
// 세션 로그에 더 늦게 리셋되는 창이 나타났고 옛 창이 그 창이 열리기 전에만 관측됐다면,
|
|
123
|
+
// 옛 창은 조기 리셋(2026-09-23 실측)이나 계정 전환으로 끝난 창이다. 프로브로만 본 창은
|
|
124
|
+
// 다른 계정일 수 있어 근거로 쓰지 않는다. 새 창이 열린 뒤에도 관측된 창은 함께 도는
|
|
125
|
+
// 계정이므로 max-used 후보로 남긴다.
|
|
126
|
+
function isReplacedWindowGroup(group, other) {
|
|
127
|
+
if (other === group || !other.seenInSessions) return false;
|
|
128
|
+
if (other.minResetAt <= group.maxResetAt || !other.windowMinutes) {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
const lastSeenMs = Date.parse(group.latest.timestamp);
|
|
132
|
+
if (!Number.isFinite(lastSeenMs)) return false;
|
|
133
|
+
// resets_at 은 실측으로 아래로 약 30초, 위로 1초 흔들리므로 가장 늦은 값으로 개시 시각을 잡는다.
|
|
134
|
+
const otherOpenedAtMs = (other.maxResetAt - other.windowMinutes * 60) * 1000;
|
|
135
|
+
return lastSeenMs <= otherOpenedAtMs;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function selectCodexWindowSnapshot(
|
|
139
|
+
snapshots,
|
|
140
|
+
nowSec,
|
|
141
|
+
windowKey,
|
|
142
|
+
replaceEndedWindows,
|
|
143
|
+
) {
|
|
108
144
|
const groups = [];
|
|
109
145
|
const ungrouped = [];
|
|
110
146
|
|
|
@@ -126,19 +162,29 @@ function selectCodexWindowSnapshot(snapshots, nowSec, windowKey) {
|
|
|
126
162
|
minResetAt: resetAt,
|
|
127
163
|
maxResetAt: resetAt,
|
|
128
164
|
latest: snapshot,
|
|
165
|
+
seenInSessions: !snapshot.probe,
|
|
166
|
+
windowMinutes: positiveWindowMinutes(snapshot[windowKey]),
|
|
129
167
|
};
|
|
130
168
|
groups.push(group);
|
|
131
169
|
continue;
|
|
132
170
|
}
|
|
133
171
|
|
|
172
|
+
if (!snapshot.probe) group.seenInSessions = true;
|
|
173
|
+
group.windowMinutes ??= positiveWindowMinutes(snapshot[windowKey]);
|
|
134
174
|
group.minResetAt = Math.min(group.minResetAt, resetAt);
|
|
135
175
|
group.maxResetAt = Math.max(group.maxResetAt, resetAt);
|
|
136
176
|
if (isNewerSnapshot(snapshot, group.latest)) group.latest = snapshot;
|
|
137
177
|
}
|
|
138
178
|
|
|
139
|
-
const
|
|
179
|
+
const openGroups = groups.filter(
|
|
140
180
|
(group) => Number(group.latest[windowKey]?.resets_at) > nowSec,
|
|
141
181
|
);
|
|
182
|
+
const activeGroups = replaceEndedWindows
|
|
183
|
+
? openGroups.filter(
|
|
184
|
+
(group) =>
|
|
185
|
+
!openGroups.some((other) => isReplacedWindowGroup(group, other)),
|
|
186
|
+
)
|
|
187
|
+
: openGroups;
|
|
142
188
|
let selected = null;
|
|
143
189
|
if (activeGroups.length > 0) {
|
|
144
190
|
selected = activeGroups
|
|
@@ -168,16 +214,18 @@ function selectCodexWindowSnapshot(snapshots, nowSec, windowKey) {
|
|
|
168
214
|
return { snapshot: selected, activeGroupCount: activeGroups.length };
|
|
169
215
|
}
|
|
170
216
|
|
|
171
|
-
function selectCodexSnapshot(snapshots, nowSec) {
|
|
217
|
+
function selectCodexSnapshot(snapshots, nowSec, replaceEndedWindows) {
|
|
172
218
|
const primarySelection = selectCodexWindowSnapshot(
|
|
173
219
|
snapshots,
|
|
174
220
|
nowSec,
|
|
175
221
|
"primary",
|
|
222
|
+
replaceEndedWindows,
|
|
176
223
|
);
|
|
177
224
|
const secondarySelection = selectCodexWindowSnapshot(
|
|
178
225
|
snapshots,
|
|
179
226
|
nowSec,
|
|
180
227
|
"secondary",
|
|
228
|
+
replaceEndedWindows,
|
|
181
229
|
);
|
|
182
230
|
const primarySnapshot = primarySelection.snapshot;
|
|
183
231
|
if (!primarySnapshot) return null;
|
|
@@ -198,7 +246,7 @@ function selectCodexSnapshot(snapshots, nowSec) {
|
|
|
198
246
|
return selected;
|
|
199
247
|
}
|
|
200
248
|
|
|
201
|
-
function mergeRateLimitSnapshots(snapshots, nowSec) {
|
|
249
|
+
function mergeRateLimitSnapshots(snapshots, nowSec, replaceEndedWindows) {
|
|
202
250
|
const merged = {};
|
|
203
251
|
const codexSnapshots = [];
|
|
204
252
|
for (const snapshot of snapshots) {
|
|
@@ -211,7 +259,11 @@ function mergeRateLimitSnapshots(snapshots, nowSec) {
|
|
|
211
259
|
}
|
|
212
260
|
}
|
|
213
261
|
|
|
214
|
-
const codex = selectCodexSnapshot(
|
|
262
|
+
const codex = selectCodexSnapshot(
|
|
263
|
+
codexSnapshots,
|
|
264
|
+
nowSec,
|
|
265
|
+
replaceEndedWindows,
|
|
266
|
+
);
|
|
215
267
|
if (codex) merged.codex = codex;
|
|
216
268
|
return merged;
|
|
217
269
|
}
|
|
@@ -227,9 +279,16 @@ export function getCodexRateLimits({
|
|
|
227
279
|
now = new Date(),
|
|
228
280
|
maxLinesPerFile = 800,
|
|
229
281
|
extraSnapshots = [],
|
|
282
|
+
// 교체된 창 판정(isReplacedWindowGroup). 브로커 Codex 계정이 있으면 끈다.
|
|
283
|
+
replaceEndedWindows = true,
|
|
230
284
|
} = {}) {
|
|
231
285
|
let syntheticBucket = null; // 최근 token_count에서 합성 (행 활성화 + 토큰 데이터용)
|
|
232
|
-
|
|
286
|
+
// 프로브도 세션 로그와 같은 window_minutes 기준으로 5h/1w 슬롯을 정한다.
|
|
287
|
+
// app-server 는 5h 창이 없으면 주간 창을 primary 로 보낸다(2026-09-23 실측).
|
|
288
|
+
const recentSnapshots = extraSnapshots.map((snapshot) => ({
|
|
289
|
+
...snapshot,
|
|
290
|
+
...normalizeBuckets(snapshot),
|
|
291
|
+
}));
|
|
233
292
|
const nowSec = Math.floor(now.getTime() / 1000);
|
|
234
293
|
|
|
235
294
|
// 7일간 스캔: 실제 rate_limits 우선, 합성 버킷은 폴백
|
|
@@ -313,7 +372,11 @@ export function getCodexRateLimits({
|
|
|
313
372
|
}
|
|
314
373
|
|
|
315
374
|
const snapshots = dayOffset <= 1 ? recentSnapshots : daySnapshots;
|
|
316
|
-
const mergedBuckets = mergeRateLimitSnapshots(
|
|
375
|
+
const mergedBuckets = mergeRateLimitSnapshots(
|
|
376
|
+
snapshots,
|
|
377
|
+
nowSec,
|
|
378
|
+
replaceEndedWindows,
|
|
379
|
+
);
|
|
317
380
|
if (Object.keys(mergedBuckets).length > 0) {
|
|
318
381
|
if (syntheticBucket) {
|
|
319
382
|
const main =
|
|
@@ -369,10 +432,30 @@ export async function collectProbeSnapshots(now = new Date()) {
|
|
|
369
432
|
);
|
|
370
433
|
}
|
|
371
434
|
|
|
435
|
+
// 브로커 계정은 자기 CODEX_HOME 에서 돌기 때문에, 옛 창이 전환 전 계정의 창이어도 그
|
|
436
|
+
// 계정이 아직 쓰이고 있을 수 있다. accounts.json 의 codex 항목과 프로브용 인증 캐시로 판정한다.
|
|
437
|
+
export function hasBrokerCodexAccounts({
|
|
438
|
+
brokerDir = CODEX_BROKER_AUTH_CACHE_DIR,
|
|
439
|
+
} = {}) {
|
|
440
|
+
const accounts = readJson(join(brokerDir, "accounts.json"), null);
|
|
441
|
+
if (Array.isArray(accounts?.codex) && accounts.codex.length > 0) return true;
|
|
442
|
+
try {
|
|
443
|
+
return readdirSync(brokerDir).some((file) =>
|
|
444
|
+
/^codex-auth-.+\.json$/.test(file),
|
|
445
|
+
);
|
|
446
|
+
} catch {
|
|
447
|
+
return false;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
372
451
|
export async function refreshCodexRateLimitsCache() {
|
|
373
452
|
const now = new Date();
|
|
374
453
|
const extraSnapshots = await collectProbeSnapshots(now);
|
|
375
|
-
const buckets = getCodexRateLimits({
|
|
454
|
+
const buckets = getCodexRateLimits({
|
|
455
|
+
now,
|
|
456
|
+
extraSnapshots,
|
|
457
|
+
replaceEndedWindows: !hasBrokerCodexAccounts(),
|
|
458
|
+
});
|
|
376
459
|
// buckets가 null이어도 캐시 갱신 (stale 데이터 제거)
|
|
377
460
|
writeJsonSafe(CODEX_QUOTA_CACHE_PATH, { timestamp: Date.now(), buckets });
|
|
378
461
|
return buckets;
|
package/hud/providers/cto.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFileSync, statSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { resolveRoleControlSnapshot } from "../../hub/lib/cto-env.mjs";
|
|
3
4
|
import {
|
|
4
5
|
CTO_LAKE_CURRENT_RELATIVE_PATH,
|
|
5
6
|
CTO_STATUS_LINE_MAX_CHARS,
|
|
@@ -53,6 +54,8 @@ function extractStatusLine(lines) {
|
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
export function readCtoStatus(options = {}) {
|
|
57
|
+
if (!resolveRoleControlSnapshot(options.env).auto_collect_enabled)
|
|
58
|
+
return null;
|
|
56
59
|
try {
|
|
57
60
|
const currentPath = resolveCurrentPath(options);
|
|
58
61
|
const stat = statSync(currentPath);
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
|
|
8
8
|
const policy = {
|
|
9
9
|
executor: {
|
|
10
|
-
profile: "
|
|
10
|
+
profile: "gpt6_sol_high",
|
|
11
11
|
timeoutSec: 1080,
|
|
12
12
|
runMode: "fg",
|
|
13
13
|
opusOversight: false,
|
|
@@ -15,7 +15,7 @@ const policy = {
|
|
|
15
15
|
subcommand: "exec",
|
|
16
16
|
},
|
|
17
17
|
codex: {
|
|
18
|
-
profile: "
|
|
18
|
+
profile: "gpt6_sol_high",
|
|
19
19
|
timeoutSec: 1080,
|
|
20
20
|
runMode: "fg",
|
|
21
21
|
opusOversight: false,
|
|
@@ -23,7 +23,7 @@ const policy = {
|
|
|
23
23
|
subcommand: "exec",
|
|
24
24
|
},
|
|
25
25
|
"build-fixer": {
|
|
26
|
-
profile: "
|
|
26
|
+
profile: "gpt6_luna_high",
|
|
27
27
|
timeoutSec: 540,
|
|
28
28
|
runMode: "fg",
|
|
29
29
|
opusOversight: false,
|
|
@@ -31,7 +31,7 @@ const policy = {
|
|
|
31
31
|
subcommand: "exec",
|
|
32
32
|
},
|
|
33
33
|
cleanup: {
|
|
34
|
-
profile: "
|
|
34
|
+
profile: "gpt6_sol_med",
|
|
35
35
|
timeoutSec: 540,
|
|
36
36
|
runMode: "fg",
|
|
37
37
|
opusOversight: false,
|
|
@@ -39,7 +39,7 @@ const policy = {
|
|
|
39
39
|
subcommand: "exec",
|
|
40
40
|
},
|
|
41
41
|
deslop: {
|
|
42
|
-
profile: "
|
|
42
|
+
profile: "gpt6_sol_med",
|
|
43
43
|
timeoutSec: 540,
|
|
44
44
|
runMode: "fg",
|
|
45
45
|
opusOversight: false,
|
|
@@ -95,7 +95,7 @@ const policy = {
|
|
|
95
95
|
subcommand: "exec",
|
|
96
96
|
},
|
|
97
97
|
"code-reviewer": {
|
|
98
|
-
profile: "
|
|
98
|
+
profile: "gpt6_sol_high",
|
|
99
99
|
timeoutSec: 1800,
|
|
100
100
|
runMode: "bg",
|
|
101
101
|
opusOversight: false,
|
|
@@ -103,7 +103,7 @@ const policy = {
|
|
|
103
103
|
subcommand: "review",
|
|
104
104
|
},
|
|
105
105
|
"quality-reviewer": {
|
|
106
|
-
profile: "
|
|
106
|
+
profile: "gpt6_sol_high",
|
|
107
107
|
timeoutSec: 1800,
|
|
108
108
|
runMode: "bg",
|
|
109
109
|
opusOversight: false,
|
|
@@ -119,7 +119,7 @@ const policy = {
|
|
|
119
119
|
subcommand: "review",
|
|
120
120
|
},
|
|
121
121
|
scientist: {
|
|
122
|
-
profile: "
|
|
122
|
+
profile: "gpt6_sol_high",
|
|
123
123
|
timeoutSec: 1440,
|
|
124
124
|
runMode: "bg",
|
|
125
125
|
opusOversight: false,
|
|
@@ -127,7 +127,7 @@ const policy = {
|
|
|
127
127
|
subcommand: "exec",
|
|
128
128
|
},
|
|
129
129
|
"document-specialist": {
|
|
130
|
-
profile: "
|
|
130
|
+
profile: "gpt6_sol_high",
|
|
131
131
|
timeoutSec: 1440,
|
|
132
132
|
runMode: "bg",
|
|
133
133
|
opusOversight: false,
|
|
@@ -143,7 +143,7 @@ const policy = {
|
|
|
143
143
|
subcommand: "exec",
|
|
144
144
|
},
|
|
145
145
|
verifier: {
|
|
146
|
-
profile: "
|
|
146
|
+
profile: "gpt6_sol_high",
|
|
147
147
|
timeoutSec: 1200,
|
|
148
148
|
runMode: "fg",
|
|
149
149
|
opusOversight: false,
|
|
@@ -151,7 +151,7 @@ const policy = {
|
|
|
151
151
|
subcommand: "review",
|
|
152
152
|
},
|
|
153
153
|
"test-engineer": {
|
|
154
|
-
profile: "
|
|
154
|
+
profile: "gpt6_sol_high",
|
|
155
155
|
timeoutSec: 1200,
|
|
156
156
|
runMode: "bg",
|
|
157
157
|
opusOversight: false,
|
|
@@ -159,7 +159,7 @@ const policy = {
|
|
|
159
159
|
subcommand: "exec",
|
|
160
160
|
},
|
|
161
161
|
"qa-tester": {
|
|
162
|
-
profile: "
|
|
162
|
+
profile: "gpt6_sol_high",
|
|
163
163
|
timeoutSec: 1200,
|
|
164
164
|
runMode: "bg",
|
|
165
165
|
opusOversight: false,
|
|
@@ -167,7 +167,7 @@ const policy = {
|
|
|
167
167
|
subcommand: "review",
|
|
168
168
|
},
|
|
169
169
|
spark: {
|
|
170
|
-
profile: "
|
|
170
|
+
profile: "gpt6_luna_low",
|
|
171
171
|
timeoutSec: 180,
|
|
172
172
|
runMode: "fg",
|
|
173
173
|
opusOversight: false,
|
|
@@ -185,7 +185,7 @@ const policy = {
|
|
|
185
185
|
subcommand: "exec",
|
|
186
186
|
},
|
|
187
187
|
writer: {
|
|
188
|
-
profile: "
|
|
188
|
+
profile: "gpt6_luna_high",
|
|
189
189
|
timeoutSec: 900,
|
|
190
190
|
runMode: "bg",
|
|
191
191
|
opusOversight: false,
|
|
@@ -210,9 +210,10 @@ export const DEFAULT_CODEX_AGENT = "executor";
|
|
|
210
210
|
|
|
211
211
|
export const CANONICAL_CODEX_PROFILE_OVERRIDES = Object.freeze(
|
|
212
212
|
new Set([
|
|
213
|
-
"
|
|
214
|
-
"
|
|
215
|
-
"
|
|
213
|
+
"gpt6_luna_low",
|
|
214
|
+
"gpt6_luna_high",
|
|
215
|
+
"gpt6_sol_med",
|
|
216
|
+
"gpt6_sol_high",
|
|
216
217
|
"gpt6_astra_xhigh",
|
|
217
218
|
"gpt6_astra_max",
|
|
218
219
|
"gpt6_astra_ultra",
|
|
@@ -19,18 +19,24 @@ const EFFORT_BY_SUFFIX = {
|
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
const CANONICAL_PROFILE_VALUES = Object.freeze({
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
gpt6_luna_low: { model: "gpt-6-luna", effort: "low" },
|
|
23
|
+
gpt6_luna_high: { model: "gpt-6-luna", effort: "high" },
|
|
24
|
+
gpt6_sol_med: { model: "gpt-6-sol", effort: "medium" },
|
|
25
|
+
gpt6_sol_high: { model: "gpt-6-sol", effort: "high" },
|
|
25
26
|
gpt6_astra_xhigh: { model: "gpt-6-astra", effort: "xhigh" },
|
|
26
27
|
gpt6_astra_max: { model: "gpt-6-astra", effort: "max" },
|
|
27
28
|
gpt6_astra_ultra: { model: "gpt-6-astra", effort: "ultra" },
|
|
28
29
|
});
|
|
29
30
|
|
|
31
|
+
// GPT-5.6 lane names map to the GPT-6 lane with the same effort. GPT-6 has no
|
|
32
|
+
// Terra tier, so Terra lanes move to Sol.
|
|
30
33
|
const LEGACY_CANONICAL_PROFILE_ALIASES = Object.freeze({
|
|
31
34
|
gpt56_sol_xhigh: "gpt6_astra_xhigh",
|
|
32
35
|
gpt56_sol_max: "gpt6_astra_max",
|
|
33
36
|
gpt56_sol_ultra: "gpt6_astra_ultra",
|
|
37
|
+
gpt56_terra_high: "gpt6_sol_high",
|
|
38
|
+
gpt56_terra_med: "gpt6_sol_med",
|
|
39
|
+
gpt56_luna_low: "gpt6_luna_low",
|
|
34
40
|
});
|
|
35
41
|
|
|
36
42
|
export function normalizeCodexProfileName(profileName) {
|
|
@@ -38,6 +44,14 @@ export function normalizeCodexProfileName(profileName) {
|
|
|
38
44
|
return LEGACY_CANONICAL_PROFILE_ALIASES[normalized] || normalized;
|
|
39
45
|
}
|
|
40
46
|
|
|
47
|
+
/** Model ID of a canonical profile (after legacy normalization), or null. */
|
|
48
|
+
export function canonicalCodexProfileModel(profileName) {
|
|
49
|
+
return (
|
|
50
|
+
CANONICAL_PROFILE_VALUES[normalizeCodexProfileName(profileName)]?.model ??
|
|
51
|
+
null
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
41
55
|
function readProfileScalar(raw, key) {
|
|
42
56
|
const match = String(raw).match(
|
|
43
57
|
new RegExp(
|
|
@@ -51,7 +65,7 @@ function readProfileScalar(raw, key) {
|
|
|
51
65
|
}
|
|
52
66
|
|
|
53
67
|
/**
|
|
54
|
-
* Resolve a Codex effort profile name (e.g. "
|
|
68
|
+
* Resolve a Codex effort profile name (e.g. "gpt6_sol_high") to the `-c key=value`
|
|
55
69
|
* config overrides that `codex exec` accepts, so the headless CLI lanes can
|
|
56
70
|
* select a profile WITHOUT `codex exec --profile <name>`. codex 0.134+ rejects
|
|
57
71
|
* `--profile X` whenever config.toml still contains an inline `[profiles.X]`
|
|
@@ -69,7 +83,7 @@ function readProfileScalar(raw, key) {
|
|
|
69
83
|
*
|
|
70
84
|
* @param {string} profileName
|
|
71
85
|
* @param {{ codexHome?: string }} [opts]
|
|
72
|
-
* @returns {string[]} e.g. ['model="gpt-
|
|
86
|
+
* @returns {string[]} e.g. ['model="gpt-6-sol"', 'model_reasoning_effort="high"']
|
|
73
87
|
*/
|
|
74
88
|
export function resolveCodexProfileConfigValues(profileName, opts = {}) {
|
|
75
89
|
if (typeof profileName !== "string" || !profileName) {
|