@triflux/core 10.43.0 → 10.44.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/keyword-rules.json +139 -19
- package/hub/cli-adapter-base.mjs +11 -2
- package/hub/codex-adapter.mjs +3 -0
- package/hub/intent.mjs +24 -32
- package/hub/router.mjs +272 -0
- package/hub/team/intervention.mjs +1 -1
- package/hub/team/retry-state-machine.mjs +8 -3
- package/hub/team/synapse-http.mjs +149 -4
- package/package.json +1 -1
- package/scripts/lib/agent-route-policy.mjs +360 -0
- package/scripts/lib/cli-codex.mjs +22 -160
- package/scripts/lib/codex-profile-config.mjs +29 -3
- package/scripts/lib/keyword-rules.mjs +12 -0
- package/hub/log-retention.mjs +0 -726
|
@@ -41,13 +41,13 @@ export const MODES = Object.freeze({
|
|
|
41
41
|
const ESCALATION_CHAIN_CONFIG_PATH = ".triflux/config/escalation-chain.json";
|
|
42
42
|
|
|
43
43
|
// Escalation chain (2026-05-27 정책):
|
|
44
|
-
// 1. codex gpt-5.6-sol —
|
|
44
|
+
// 1. codex gpt-5.6-sol max — 최난도 단일 작업용 Codex escalation 단계
|
|
45
45
|
// 2. claude opus-4-8 — 최종 수단
|
|
46
46
|
const DEFAULT_ESCALATION_CHAIN = Object.freeze([
|
|
47
47
|
Object.freeze({
|
|
48
48
|
cli: "codex",
|
|
49
49
|
model: "gpt-5.6-sol",
|
|
50
|
-
profile: "
|
|
50
|
+
profile: "gpt56_sol_max",
|
|
51
51
|
}),
|
|
52
52
|
Object.freeze({ cli: "claude", model: "opus-4-8" }),
|
|
53
53
|
]);
|
|
@@ -295,7 +295,12 @@ function normalizeEscalationChainEntry(entry, index, source) {
|
|
|
295
295
|
model: entry.model,
|
|
296
296
|
};
|
|
297
297
|
if (typeof entry.profile === "string" && entry.profile.trim() !== "") {
|
|
298
|
-
|
|
298
|
+
const profile = entry.profile.trim();
|
|
299
|
+
normalized.profile =
|
|
300
|
+
entry.cli === "codex" &&
|
|
301
|
+
(profile === "ultra" || profile === "gpt56_sol_ultra")
|
|
302
|
+
? "gpt56_sol_max"
|
|
303
|
+
: profile;
|
|
299
304
|
}
|
|
300
305
|
return normalized;
|
|
301
306
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
+
import { homedir, hostname } from "node:os";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
import { normalizeRoleKey } from "../role-contract.mjs";
|
|
4
7
|
|
|
5
8
|
const HUB_DEFAULT_PORT = 27888;
|
|
6
9
|
// Same token file as hub/bridge.mjs (HUB_TOKEN_FILE). Kept inline rather than
|
|
@@ -21,6 +24,144 @@ function normalizeToken(raw) {
|
|
|
21
24
|
return token || null;
|
|
22
25
|
}
|
|
23
26
|
|
|
27
|
+
function opaqueId(prefix, value) {
|
|
28
|
+
return `${prefix}_${createHash("sha256")
|
|
29
|
+
.update(String(value || ""))
|
|
30
|
+
.digest("base64url")
|
|
31
|
+
.slice(0, 22)}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function normalizedProjectId(value) {
|
|
35
|
+
try {
|
|
36
|
+
return normalizeRoleKey({ project_id: value, role_kind: "cto" }).project_id;
|
|
37
|
+
} catch {
|
|
38
|
+
return "";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function resolveProjectId(cwd, meta, opts = {}) {
|
|
43
|
+
const explicit = meta?.project_id || process.env.TFX_PROJECT_ID;
|
|
44
|
+
if (explicit) return normalizedProjectId(explicit);
|
|
45
|
+
if (typeof opts.resolveProjectId === "function") {
|
|
46
|
+
return normalizedProjectId(opts.resolveProjectId(cwd));
|
|
47
|
+
}
|
|
48
|
+
let current = cwd;
|
|
49
|
+
while (current) {
|
|
50
|
+
const manifest = join(current, ".triflux", "project.json");
|
|
51
|
+
if (existsSync(manifest)) {
|
|
52
|
+
try {
|
|
53
|
+
const parsed = JSON.parse(readFileSync(manifest, "utf8"));
|
|
54
|
+
if (parsed?.schema_version !== "tfx.project.v1") return "";
|
|
55
|
+
return normalizedProjectId(parsed.project_id);
|
|
56
|
+
} catch {
|
|
57
|
+
return "";
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const parent = dirname(current);
|
|
61
|
+
if (parent === current) break;
|
|
62
|
+
current = parent;
|
|
63
|
+
}
|
|
64
|
+
return "";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function inferSessionCli(meta, opts = {}) {
|
|
68
|
+
const explicit = String(meta?.cli || meta?.actor_cli || "")
|
|
69
|
+
.trim()
|
|
70
|
+
.toLowerCase();
|
|
71
|
+
if (explicit === "codex" || explicit === "claude") return explicit;
|
|
72
|
+
const entrypoint = String(opts.entrypoint ?? process.argv[1] ?? "");
|
|
73
|
+
if (entrypoint.includes("codex-session-hook")) return "codex";
|
|
74
|
+
if (entrypoint.includes("agy-session-hook")) return "other";
|
|
75
|
+
return "claude";
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function resolveTmuxSession(meta, opts = {}) {
|
|
79
|
+
const explicit =
|
|
80
|
+
meta?.tmux_session || meta?.session_name || process.env.TFX_TMUX_SESSION;
|
|
81
|
+
if (explicit) return String(explicit).trim();
|
|
82
|
+
if (!process.env.TMUX) return "";
|
|
83
|
+
try {
|
|
84
|
+
const run =
|
|
85
|
+
opts.tmuxSessionName ||
|
|
86
|
+
(() =>
|
|
87
|
+
execFileSync("tmux", ["display-message", "-p", "#{session_name}"], {
|
|
88
|
+
encoding: "utf8",
|
|
89
|
+
timeout: 200,
|
|
90
|
+
windowsHide: true,
|
|
91
|
+
}));
|
|
92
|
+
return String(run() || "").trim();
|
|
93
|
+
} catch {
|
|
94
|
+
return "";
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function buildSynapseRegistrationMeta(meta, opts = {}) {
|
|
99
|
+
if (!meta || meta.sessionKind !== "interactive") return meta;
|
|
100
|
+
const sessionId = String(meta.sessionId || "").trim();
|
|
101
|
+
if (!sessionId) return meta;
|
|
102
|
+
const cwd = typeof meta.cwd === "string" ? meta.cwd : process.cwd();
|
|
103
|
+
const cli = inferSessionCli(meta, opts);
|
|
104
|
+
const hostId = String(
|
|
105
|
+
meta.host_id ||
|
|
106
|
+
process.env.TFX_HOST_ID ||
|
|
107
|
+
opaqueId("hst", opts.hostname?.() || hostname()),
|
|
108
|
+
).trim();
|
|
109
|
+
const expiresAtMs = Number(opts.nowMs?.() ?? Date.now()) + 299000;
|
|
110
|
+
const transportLocators = [];
|
|
111
|
+
if (cli === "claude") {
|
|
112
|
+
transportLocators.push({
|
|
113
|
+
schema_version: "tfx.transport-locator.v1",
|
|
114
|
+
transport_id: opaqueId("trn", `claude-uds:${hostId}:${sessionId}`),
|
|
115
|
+
kind: "claude-uds",
|
|
116
|
+
host_id: hostId,
|
|
117
|
+
capabilities: ["probe", "wake", "activate", "interrupt"],
|
|
118
|
+
priority: 100,
|
|
119
|
+
expires_at_ms: expiresAtMs,
|
|
120
|
+
locator: {
|
|
121
|
+
session_id: sessionId,
|
|
122
|
+
bridge_id: String(
|
|
123
|
+
meta.bridge_id || process.env.TFX_BRIDGE_ID || "local-hub",
|
|
124
|
+
),
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
const tmuxSession = resolveTmuxSession(meta, opts);
|
|
129
|
+
if (tmuxSession) {
|
|
130
|
+
const muxServerSource =
|
|
131
|
+
meta.mux_server_id ||
|
|
132
|
+
process.env.TFX_MUX_SERVER_ID ||
|
|
133
|
+
String(process.env.TMUX || "default").split(",", 1)[0];
|
|
134
|
+
transportLocators.push({
|
|
135
|
+
schema_version: "tfx.transport-locator.v1",
|
|
136
|
+
transport_id: opaqueId("trn", `tmux:${hostId}:${tmuxSession}`),
|
|
137
|
+
kind: "tmux",
|
|
138
|
+
host_id: hostId,
|
|
139
|
+
capabilities: ["probe", "wake", "activate", "interrupt"],
|
|
140
|
+
priority: cli === "codex" ? 100 : 50,
|
|
141
|
+
expires_at_ms: expiresAtMs,
|
|
142
|
+
locator: {
|
|
143
|
+
session_name: tmuxSession,
|
|
144
|
+
mux_server_id: opaqueId("mux", muxServerSource),
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
const directAgentId = `session:${sessionId}`;
|
|
149
|
+
return {
|
|
150
|
+
...meta,
|
|
151
|
+
agent_id:
|
|
152
|
+
meta.agent_id ||
|
|
153
|
+
(directAgentId.length <= 64 && /^[a-zA-Z0-9._:-]+$/u.test(directAgentId)
|
|
154
|
+
? directAgentId
|
|
155
|
+
: opaqueId("session", sessionId)),
|
|
156
|
+
cli,
|
|
157
|
+
project_id: resolveProjectId(cwd, meta, opts) || undefined,
|
|
158
|
+
session_id: meta.session_id || sessionId,
|
|
159
|
+
host_id: hostId,
|
|
160
|
+
transport_locators_json:
|
|
161
|
+
meta.transport_locators_json ?? JSON.stringify(transportLocators),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
24
165
|
// Mirrors hub/bridge.mjs:readHubToken — env override first, then the token file.
|
|
25
166
|
// Missing token → null (backward-compatible with token-less hubs).
|
|
26
167
|
function readHubToken() {
|
|
@@ -139,7 +280,11 @@ export function drainPendingSynapse(timeoutMs = 1000) {
|
|
|
139
280
|
}
|
|
140
281
|
|
|
141
282
|
export function registerSynapseSession(meta, opts = {}) {
|
|
142
|
-
return fireAndForgetSynapse(
|
|
283
|
+
return fireAndForgetSynapse(
|
|
284
|
+
"/synapse/register",
|
|
285
|
+
buildSynapseRegistrationMeta(meta, opts),
|
|
286
|
+
opts,
|
|
287
|
+
);
|
|
143
288
|
}
|
|
144
289
|
|
|
145
290
|
export function heartbeatSynapseSession(
|
package/package.json
CHANGED
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
// Canonical Codex role policy. Concrete model IDs stay in profile config files.
|
|
2
|
+
|
|
3
|
+
import { resolveCodexProfileConfigValues } from "./codex-profile-config.mjs";
|
|
4
|
+
|
|
5
|
+
const policy = {
|
|
6
|
+
executor: {
|
|
7
|
+
profile: "gpt56_terra_high",
|
|
8
|
+
timeoutSec: 1080,
|
|
9
|
+
runMode: "fg",
|
|
10
|
+
opusOversight: false,
|
|
11
|
+
mcpHint: "implement",
|
|
12
|
+
subcommand: "exec",
|
|
13
|
+
},
|
|
14
|
+
codex: {
|
|
15
|
+
profile: "gpt56_terra_high",
|
|
16
|
+
timeoutSec: 1080,
|
|
17
|
+
runMode: "fg",
|
|
18
|
+
opusOversight: false,
|
|
19
|
+
mcpHint: "implement",
|
|
20
|
+
subcommand: "exec",
|
|
21
|
+
},
|
|
22
|
+
"build-fixer": {
|
|
23
|
+
profile: "gpt56_luna_low",
|
|
24
|
+
timeoutSec: 540,
|
|
25
|
+
runMode: "fg",
|
|
26
|
+
opusOversight: false,
|
|
27
|
+
mcpHint: "implement",
|
|
28
|
+
subcommand: "exec",
|
|
29
|
+
},
|
|
30
|
+
cleanup: {
|
|
31
|
+
profile: "gpt56_terra_med",
|
|
32
|
+
timeoutSec: 540,
|
|
33
|
+
runMode: "fg",
|
|
34
|
+
opusOversight: false,
|
|
35
|
+
mcpHint: "implement",
|
|
36
|
+
subcommand: "exec",
|
|
37
|
+
},
|
|
38
|
+
deslop: {
|
|
39
|
+
profile: "gpt56_terra_med",
|
|
40
|
+
timeoutSec: 540,
|
|
41
|
+
runMode: "fg",
|
|
42
|
+
opusOversight: false,
|
|
43
|
+
mcpHint: "implement",
|
|
44
|
+
subcommand: "exec",
|
|
45
|
+
},
|
|
46
|
+
debugger: {
|
|
47
|
+
profile: "gpt56_sol_xhigh",
|
|
48
|
+
timeoutSec: 900,
|
|
49
|
+
runMode: "bg",
|
|
50
|
+
opusOversight: false,
|
|
51
|
+
mcpHint: "implement",
|
|
52
|
+
subcommand: "exec",
|
|
53
|
+
},
|
|
54
|
+
"deep-executor": {
|
|
55
|
+
profile: "gpt56_sol_xhigh",
|
|
56
|
+
timeoutSec: 3600,
|
|
57
|
+
runMode: "bg",
|
|
58
|
+
opusOversight: true,
|
|
59
|
+
mcpHint: "implement",
|
|
60
|
+
subcommand: "exec",
|
|
61
|
+
},
|
|
62
|
+
architect: {
|
|
63
|
+
profile: "gpt56_sol_xhigh",
|
|
64
|
+
timeoutSec: 3600,
|
|
65
|
+
runMode: "bg",
|
|
66
|
+
opusOversight: true,
|
|
67
|
+
mcpHint: "analyze",
|
|
68
|
+
subcommand: "exec",
|
|
69
|
+
},
|
|
70
|
+
critic: {
|
|
71
|
+
profile: "gpt56_sol_xhigh",
|
|
72
|
+
timeoutSec: 3600,
|
|
73
|
+
runMode: "bg",
|
|
74
|
+
opusOversight: true,
|
|
75
|
+
mcpHint: "analyze",
|
|
76
|
+
subcommand: "exec",
|
|
77
|
+
},
|
|
78
|
+
planner: {
|
|
79
|
+
profile: "gpt56_sol_xhigh",
|
|
80
|
+
timeoutSec: 3600,
|
|
81
|
+
runMode: "fg",
|
|
82
|
+
opusOversight: true,
|
|
83
|
+
mcpHint: "analyze",
|
|
84
|
+
subcommand: "exec",
|
|
85
|
+
},
|
|
86
|
+
analyst: {
|
|
87
|
+
profile: "gpt56_sol_xhigh",
|
|
88
|
+
timeoutSec: 3600,
|
|
89
|
+
runMode: "fg",
|
|
90
|
+
opusOversight: true,
|
|
91
|
+
mcpHint: "analyze",
|
|
92
|
+
subcommand: "exec",
|
|
93
|
+
},
|
|
94
|
+
"code-reviewer": {
|
|
95
|
+
profile: "gpt56_terra_high",
|
|
96
|
+
timeoutSec: 1800,
|
|
97
|
+
runMode: "bg",
|
|
98
|
+
opusOversight: false,
|
|
99
|
+
mcpHint: "review",
|
|
100
|
+
subcommand: "review",
|
|
101
|
+
},
|
|
102
|
+
"quality-reviewer": {
|
|
103
|
+
profile: "gpt56_terra_high",
|
|
104
|
+
timeoutSec: 1800,
|
|
105
|
+
runMode: "bg",
|
|
106
|
+
opusOversight: false,
|
|
107
|
+
mcpHint: "review",
|
|
108
|
+
subcommand: "review",
|
|
109
|
+
},
|
|
110
|
+
"security-reviewer": {
|
|
111
|
+
profile: "gpt56_sol_xhigh",
|
|
112
|
+
timeoutSec: 1800,
|
|
113
|
+
runMode: "bg",
|
|
114
|
+
opusOversight: true,
|
|
115
|
+
mcpHint: "review",
|
|
116
|
+
subcommand: "review",
|
|
117
|
+
},
|
|
118
|
+
scientist: {
|
|
119
|
+
profile: "gpt56_terra_high",
|
|
120
|
+
timeoutSec: 1440,
|
|
121
|
+
runMode: "bg",
|
|
122
|
+
opusOversight: false,
|
|
123
|
+
mcpHint: "analyze",
|
|
124
|
+
subcommand: "exec",
|
|
125
|
+
},
|
|
126
|
+
"document-specialist": {
|
|
127
|
+
profile: "gpt56_terra_high",
|
|
128
|
+
timeoutSec: 1440,
|
|
129
|
+
runMode: "bg",
|
|
130
|
+
opusOversight: false,
|
|
131
|
+
mcpHint: "analyze",
|
|
132
|
+
subcommand: "exec",
|
|
133
|
+
},
|
|
134
|
+
"scientist-deep": {
|
|
135
|
+
profile: "gpt56_sol_xhigh",
|
|
136
|
+
timeoutSec: 3600,
|
|
137
|
+
runMode: "bg",
|
|
138
|
+
opusOversight: false,
|
|
139
|
+
mcpHint: "analyze",
|
|
140
|
+
subcommand: "exec",
|
|
141
|
+
},
|
|
142
|
+
verifier: {
|
|
143
|
+
profile: "gpt56_terra_high",
|
|
144
|
+
timeoutSec: 1200,
|
|
145
|
+
runMode: "fg",
|
|
146
|
+
opusOversight: false,
|
|
147
|
+
mcpHint: "review",
|
|
148
|
+
subcommand: "review",
|
|
149
|
+
},
|
|
150
|
+
"test-engineer": {
|
|
151
|
+
profile: "gpt56_terra_high",
|
|
152
|
+
timeoutSec: 1200,
|
|
153
|
+
runMode: "bg",
|
|
154
|
+
opusOversight: false,
|
|
155
|
+
mcpHint: "implement",
|
|
156
|
+
subcommand: "exec",
|
|
157
|
+
},
|
|
158
|
+
"qa-tester": {
|
|
159
|
+
profile: "gpt56_terra_high",
|
|
160
|
+
timeoutSec: 1200,
|
|
161
|
+
runMode: "bg",
|
|
162
|
+
opusOversight: false,
|
|
163
|
+
mcpHint: "review",
|
|
164
|
+
subcommand: "review",
|
|
165
|
+
},
|
|
166
|
+
spark: {
|
|
167
|
+
profile: "gpt56_luna_low",
|
|
168
|
+
timeoutSec: 180,
|
|
169
|
+
runMode: "fg",
|
|
170
|
+
opusOversight: false,
|
|
171
|
+
mcpHint: "implement",
|
|
172
|
+
subcommand: "exec",
|
|
173
|
+
},
|
|
174
|
+
// These entries are only for explicit direct-Codex overrides. agent-map.json
|
|
175
|
+
// remains the provider-selection authority for their normal execution.
|
|
176
|
+
designer: {
|
|
177
|
+
profile: "gpt56_sol_xhigh",
|
|
178
|
+
timeoutSec: 900,
|
|
179
|
+
runMode: "bg",
|
|
180
|
+
opusOversight: false,
|
|
181
|
+
mcpHint: "implement",
|
|
182
|
+
subcommand: "exec",
|
|
183
|
+
},
|
|
184
|
+
writer: {
|
|
185
|
+
profile: "gpt56_luna_low",
|
|
186
|
+
timeoutSec: 900,
|
|
187
|
+
runMode: "bg",
|
|
188
|
+
opusOversight: false,
|
|
189
|
+
mcpHint: "implement",
|
|
190
|
+
subcommand: "exec",
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
function freezePolicy(entry) {
|
|
195
|
+
return Object.freeze({ ...entry });
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export const CODEX_AGENT_POLICY = Object.freeze(
|
|
199
|
+
Object.fromEntries(
|
|
200
|
+
Object.entries(policy).map(([agent, entry]) => [
|
|
201
|
+
agent,
|
|
202
|
+
freezePolicy(entry),
|
|
203
|
+
]),
|
|
204
|
+
),
|
|
205
|
+
);
|
|
206
|
+
export const DEFAULT_CODEX_AGENT = "executor";
|
|
207
|
+
|
|
208
|
+
export const CANONICAL_CODEX_PROFILE_OVERRIDES = Object.freeze(
|
|
209
|
+
new Set([
|
|
210
|
+
"gpt56_luna_low",
|
|
211
|
+
"gpt56_terra_med",
|
|
212
|
+
"gpt56_terra_high",
|
|
213
|
+
"gpt56_sol_xhigh",
|
|
214
|
+
"gpt56_sol_max",
|
|
215
|
+
"gpt56_sol_ultra",
|
|
216
|
+
]),
|
|
217
|
+
);
|
|
218
|
+
export const ULTRA_ELIGIBLE_CODEX_AGENTS = Object.freeze(
|
|
219
|
+
new Set(["deep-executor", "scientist-deep"]),
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
const NESTED_GLOBAL_PROFILE_OVERRIDES = new Set([
|
|
223
|
+
"max",
|
|
224
|
+
"ultra",
|
|
225
|
+
"gpt56_sol_max",
|
|
226
|
+
"gpt56_sol_ultra",
|
|
227
|
+
]);
|
|
228
|
+
|
|
229
|
+
export function resolveCodexAgentPolicy(agent) {
|
|
230
|
+
return freezePolicy(
|
|
231
|
+
CODEX_AGENT_POLICY[agent] ?? CODEX_AGENT_POLICY[DEFAULT_CODEX_AGENT],
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Resolves a requested Codex profile against the canonical role policy.
|
|
237
|
+
*
|
|
238
|
+
* `retryProfile` intentionally wins over `profileOverride`: retry snapshots
|
|
239
|
+
* represent an already-selected escalation step and must not be replaced by a
|
|
240
|
+
* later process-level `TFX_CODEX_PROFILE`. Ultra is reserved for an eligible
|
|
241
|
+
* top-level role; every nested/sandboxed or ineligible request is max.
|
|
242
|
+
*/
|
|
243
|
+
export function resolveCodexAgentProfile(
|
|
244
|
+
agent,
|
|
245
|
+
{
|
|
246
|
+
profileOverride = "auto",
|
|
247
|
+
retryProfile = null,
|
|
248
|
+
nested = false,
|
|
249
|
+
allowCustom = false,
|
|
250
|
+
} = {},
|
|
251
|
+
) {
|
|
252
|
+
const defaultProfile = resolveCodexAgentPolicy(agent).profile;
|
|
253
|
+
const retryRequested = String(retryProfile ?? "").trim();
|
|
254
|
+
const requested = retryRequested || String(profileOverride || "auto").trim();
|
|
255
|
+
if (!requested || requested === "auto") return defaultProfile;
|
|
256
|
+
|
|
257
|
+
const canonical =
|
|
258
|
+
requested === "max"
|
|
259
|
+
? "gpt56_sol_max"
|
|
260
|
+
: requested === "ultra"
|
|
261
|
+
? "gpt56_sol_ultra"
|
|
262
|
+
: requested;
|
|
263
|
+
if (!CANONICAL_CODEX_PROFILE_OVERRIDES.has(canonical)) {
|
|
264
|
+
if (allowCustom) return requested;
|
|
265
|
+
throw new Error(
|
|
266
|
+
`[agent-route-policy] unsupported profile override: ${requested}`,
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
if (canonical !== "gpt56_sol_ultra") return canonical;
|
|
270
|
+
return nested || !ULTRA_ELIGIBLE_CODEX_AGENTS.has(agent)
|
|
271
|
+
? "gpt56_sol_max"
|
|
272
|
+
: canonical;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Nested headless, team, worker, and delegator launches always select an
|
|
277
|
+
* explicit role profile rather than inheriting the global Codex config. A
|
|
278
|
+
* custom profile that resolves to ultra is also downgraded to max.
|
|
279
|
+
*/
|
|
280
|
+
export function resolveNestedCodexAgentProfile(
|
|
281
|
+
agent,
|
|
282
|
+
{
|
|
283
|
+
profileOverride = "auto",
|
|
284
|
+
globalProfile = "auto",
|
|
285
|
+
retryProfile = null,
|
|
286
|
+
codexHome,
|
|
287
|
+
} = {},
|
|
288
|
+
) {
|
|
289
|
+
const explicitProfile = String(profileOverride || "auto").trim();
|
|
290
|
+
const globalOverride = String(globalProfile || "auto").trim();
|
|
291
|
+
// Headless lanes must select their role policy explicitly. Only the max and
|
|
292
|
+
// ultra exception lanes may cross the process environment boundary.
|
|
293
|
+
const effectiveOverride =
|
|
294
|
+
explicitProfile !== "auto"
|
|
295
|
+
? explicitProfile
|
|
296
|
+
: NESTED_GLOBAL_PROFILE_OVERRIDES.has(globalOverride)
|
|
297
|
+
? globalOverride
|
|
298
|
+
: "auto";
|
|
299
|
+
const fallback = resolveCodexAgentProfile(agent);
|
|
300
|
+
const candidate = resolveCodexAgentProfile(agent, {
|
|
301
|
+
profileOverride: effectiveOverride,
|
|
302
|
+
retryProfile,
|
|
303
|
+
nested: true,
|
|
304
|
+
allowCustom: true,
|
|
305
|
+
});
|
|
306
|
+
const { effort } = resolveCodexProfileConfigValues(candidate, { codexHome });
|
|
307
|
+
if (!effort) return fallback;
|
|
308
|
+
return effort.toLowerCase() === "ultra" ? "gpt56_sol_max" : candidate;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export function describeCodexAgentPolicy() {
|
|
312
|
+
return {
|
|
313
|
+
defaultAgent: DEFAULT_CODEX_AGENT,
|
|
314
|
+
agents: Object.keys(CODEX_AGENT_POLICY),
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function parseCliArgs(argv) {
|
|
319
|
+
const args = [...argv];
|
|
320
|
+
let format = "json";
|
|
321
|
+
let agent = DEFAULT_CODEX_AGENT;
|
|
322
|
+
while (args.length > 0) {
|
|
323
|
+
const flag = args.shift();
|
|
324
|
+
if (flag === "--format") format = args.shift() || "";
|
|
325
|
+
else if (flag === "--agent") agent = args.shift() || "";
|
|
326
|
+
else throw new Error(`unknown argument: ${flag}`);
|
|
327
|
+
}
|
|
328
|
+
if (!new Set(["json", "tsv"]).has(format)) {
|
|
329
|
+
throw new Error(`unsupported format: ${format}`);
|
|
330
|
+
}
|
|
331
|
+
return { format, agent };
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function runCli() {
|
|
335
|
+
const { format, agent } = parseCliArgs(process.argv.slice(2));
|
|
336
|
+
const resolved = resolveCodexAgentPolicy(agent);
|
|
337
|
+
if (format === "tsv") {
|
|
338
|
+
process.stdout.write(
|
|
339
|
+
[
|
|
340
|
+
resolved.profile,
|
|
341
|
+
resolved.timeoutSec,
|
|
342
|
+
resolved.runMode,
|
|
343
|
+
resolved.opusOversight,
|
|
344
|
+
resolved.mcpHint,
|
|
345
|
+
resolved.subcommand,
|
|
346
|
+
].join("\t"),
|
|
347
|
+
);
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
process.stdout.write(`${JSON.stringify(resolved)}\n`);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (import.meta.url === new URL(process.argv[1], "file:").href) {
|
|
354
|
+
try {
|
|
355
|
+
runCli();
|
|
356
|
+
} catch (error) {
|
|
357
|
+
process.stderr.write(`[agent-route-policy] ${error.message}\n`);
|
|
358
|
+
process.exitCode = 1;
|
|
359
|
+
}
|
|
360
|
+
}
|