@triflux/core 10.42.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.
@@ -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
+ }
@@ -5,6 +5,7 @@
5
5
  //
6
6
  // 출처: scripts/tfx-route.sh route_agent() L1052-L1059, run_codex_exec() L2036-L2046.
7
7
  // .claude/rules/tfx-update-logic.md Antigravity CLI 1.0.0 정밀 sanity matrix.
8
+ // timeoutSec은 lane 예상 소요 시간(advisory)이다. hard ceiling 집행은 Bash route가 소유한다.
8
9
 
9
10
  export const id = "agy";
10
11
  export const cliType = "antigravity";
@@ -5,6 +5,7 @@
5
5
  //
6
6
  // 출처: scripts/tfx-route.sh route_agent() L1062-L1063 (explore|claude),
7
7
  // L1196-L1203 (TFX_CLI_MODE=gemini fallback), TFX_VERIFIER_OVERRIDE=claude.
8
+ // timeoutSec은 lane 예상 소요 시간(advisory)이다. hard ceiling 집행은 Bash route가 소유한다.
8
9
 
9
10
  export const id = "claude";
10
11
  export const cliType = "claude-native";
@@ -4,168 +4,23 @@
4
4
  // 산출한다. 실제 spawn 은 Phase 1 에서 이관된다 (현재는 tfx-route.sh 가 수행).
5
5
  //
6
6
  // 진입점: tfx-route.mjs → selectAdapter("codex") → plan({...}).
7
- // 출처: scripts/tfx-route.sh route_agent() L1001-L1079 case 문 미러.
7
+ // 출처: agent-route-policy.mjs 의 canonical Codex role policy.
8
+ // timeoutSec은 lane 예상 소요 시간(advisory)이다. hard ceiling 집행은 Bash route가 소유한다.
8
9
 
9
10
  export const id = "codex";
10
11
  export const cliType = "codex";
11
12
  export const command = "codex";
12
13
 
13
- const AGENT_PROFILES = {
14
- executor: {
15
- profile: "gpt55_high",
16
- timeoutSec: 1080,
17
- runMode: "fg",
18
- opusOversight: false,
19
- mcpHint: "implement",
20
- },
21
- codex: {
22
- profile: "gpt55_high",
23
- timeoutSec: 1080,
24
- runMode: "fg",
25
- opusOversight: false,
26
- mcpHint: "implement",
27
- },
28
- "build-fixer": {
29
- profile: "gpt55_low",
30
- timeoutSec: 540,
31
- runMode: "fg",
32
- opusOversight: false,
33
- mcpHint: "implement",
34
- },
35
- cleanup: {
36
- profile: "gpt55_med",
37
- timeoutSec: 540,
38
- runMode: "fg",
39
- opusOversight: false,
40
- mcpHint: "implement",
41
- },
42
- deslop: {
43
- profile: "gpt55_med",
44
- timeoutSec: 540,
45
- runMode: "fg",
46
- opusOversight: false,
47
- mcpHint: "implement",
48
- },
49
- debugger: {
50
- profile: "gpt55_xhigh",
51
- timeoutSec: 900,
52
- runMode: "bg",
53
- opusOversight: false,
54
- mcpHint: "implement",
55
- },
56
- "deep-executor": {
57
- profile: "gpt55_xhigh",
58
- timeoutSec: 3600,
59
- runMode: "bg",
60
- opusOversight: true,
61
- mcpHint: "implement",
62
- },
63
- architect: {
64
- profile: "gpt55_xhigh",
65
- timeoutSec: 3600,
66
- runMode: "bg",
67
- opusOversight: true,
68
- mcpHint: "analyze",
69
- },
70
- critic: {
71
- profile: "gpt55_xhigh",
72
- timeoutSec: 3600,
73
- runMode: "bg",
74
- opusOversight: true,
75
- mcpHint: "analyze",
76
- },
77
- planner: {
78
- profile: "gpt55_xhigh",
79
- timeoutSec: 3600,
80
- runMode: "fg",
81
- opusOversight: true,
82
- mcpHint: "analyze",
83
- },
84
- analyst: {
85
- profile: "gpt55_xhigh",
86
- timeoutSec: 3600,
87
- runMode: "fg",
88
- opusOversight: true,
89
- mcpHint: "analyze",
90
- },
91
- "code-reviewer": {
92
- profile: "gpt55_high",
93
- timeoutSec: 1800,
94
- runMode: "bg",
95
- opusOversight: false,
96
- mcpHint: "review",
97
- subcommand: "review",
98
- },
99
- "quality-reviewer": {
100
- profile: "gpt55_high",
101
- timeoutSec: 1800,
102
- runMode: "bg",
103
- opusOversight: false,
104
- mcpHint: "review",
105
- subcommand: "review",
106
- },
107
- "security-reviewer": {
108
- profile: "gpt55_xhigh",
109
- timeoutSec: 1800,
110
- runMode: "bg",
111
- opusOversight: true,
112
- mcpHint: "review",
113
- subcommand: "review",
114
- },
115
- scientist: {
116
- profile: "gpt55_high",
117
- timeoutSec: 1440,
118
- runMode: "bg",
119
- opusOversight: false,
120
- mcpHint: "analyze",
121
- },
122
- "document-specialist": {
123
- profile: "gpt55_high",
124
- timeoutSec: 1440,
125
- runMode: "bg",
126
- opusOversight: false,
127
- mcpHint: "analyze",
128
- },
129
- "scientist-deep": {
130
- profile: "gpt55_xhigh",
131
- timeoutSec: 3600,
132
- runMode: "bg",
133
- opusOversight: false,
134
- mcpHint: "analyze",
135
- },
136
- verifier: {
137
- profile: "gpt55_high",
138
- timeoutSec: 1200,
139
- runMode: "fg",
140
- opusOversight: false,
141
- mcpHint: "review",
142
- subcommand: "review",
143
- },
144
- "test-engineer": {
145
- profile: "gpt55_high",
146
- timeoutSec: 1200,
147
- runMode: "bg",
148
- opusOversight: false,
149
- mcpHint: "implement",
150
- },
151
- "qa-tester": {
152
- profile: "gpt55_high",
153
- timeoutSec: 1200,
154
- runMode: "bg",
155
- opusOversight: false,
156
- mcpHint: "review",
157
- subcommand: "review",
158
- },
159
- spark: {
160
- profile: "gpt55_low",
161
- timeoutSec: 180,
162
- runMode: "fg",
163
- opusOversight: false,
164
- mcpHint: "implement",
165
- },
166
- };
14
+ import {
15
+ CODEX_AGENT_POLICY,
16
+ resolveCodexAgentPolicy,
17
+ resolveCodexAgentProfile,
18
+ resolveNestedCodexAgentProfile,
19
+ } from "./agent-route-policy.mjs";
167
20
 
168
- const DEFAULT_PROFILE = AGENT_PROFILES.executor;
21
+ // Compatibility re-exports for launchers which historically imported these
22
+ // helpers from this adapter. The policy module owns the semantics.
23
+ export { resolveCodexAgentProfile, resolveNestedCodexAgentProfile };
169
24
 
170
25
  export function plan({
171
26
  agent,
@@ -173,18 +28,26 @@ export function plan({
173
28
  mcpProfile = "auto",
174
29
  timeoutSec,
175
30
  contextFile,
31
+ profileOverride = "auto",
32
+ retryProfile = null,
33
+ nested = false,
176
34
  } = {}) {
177
35
  if (!agent) {
178
36
  throw new Error("[cli-codex] agent required");
179
37
  }
180
- const cfg = AGENT_PROFILES[agent] ?? DEFAULT_PROFILE;
38
+ const cfg = resolveCodexAgentPolicy(agent);
39
+ const profile = resolveCodexAgentProfile(agent, {
40
+ profileOverride,
41
+ retryProfile,
42
+ nested,
43
+ });
181
44
  const effectiveTimeoutSec =
182
45
  Number.isFinite(timeoutSec) && timeoutSec > 0 ? timeoutSec : cfg.timeoutSec;
183
46
  return {
184
47
  command,
185
48
  subcommand: cfg.subcommand ?? "exec",
186
- profile: cfg.profile,
187
- effort: cfg.profile,
49
+ profile,
50
+ effort: profile,
188
51
  timeoutMs: effectiveTimeoutSec * 1000,
189
52
  runMode: cfg.runMode,
190
53
  opusOversight: cfg.opusOversight,
@@ -195,5 +58,5 @@ export function plan({
195
58
  }
196
59
 
197
60
  export function describe() {
198
- return { id, cliType, command, agents: Object.keys(AGENT_PROFILES) };
61
+ return { id, cliType, command, agents: Object.keys(CODEX_AGENT_POLICY) };
199
62
  }
@@ -9,6 +9,8 @@ import { homedir } from "node:os";
9
9
  import { dirname, join } from "node:path";
10
10
 
11
11
  const EFFORT_BY_SUFFIX = {
12
+ ultra: "ultra",
13
+ max: "max",
12
14
  xhigh: "xhigh",
13
15
  high: "high",
14
16
  med: "medium",
@@ -16,6 +18,15 @@ const EFFORT_BY_SUFFIX = {
16
18
  low: "low",
17
19
  };
18
20
 
21
+ const CANONICAL_PROFILE_VALUES = Object.freeze({
22
+ gpt56_luna_low: { model: "gpt-5.6-luna", effort: "low" },
23
+ gpt56_terra_med: { model: "gpt-5.6-terra", effort: "medium" },
24
+ gpt56_terra_high: { model: "gpt-5.6-terra", effort: "high" },
25
+ gpt56_sol_xhigh: { model: "gpt-5.6-sol", effort: "xhigh" },
26
+ gpt56_sol_max: { model: "gpt-5.6-sol", effort: "max" },
27
+ gpt56_sol_ultra: { model: "gpt-5.6-sol", effort: "ultra" },
28
+ });
29
+
19
30
  function readProfileScalar(raw, key) {
20
31
  const match = String(raw).match(
21
32
  new RegExp(
@@ -29,7 +40,7 @@ function readProfileScalar(raw, key) {
29
40
  }
30
41
 
31
42
  /**
32
- * Resolve a Codex effort profile name (e.g. "gpt55_high") to the `-c key=value`
43
+ * Resolve a Codex effort profile name (e.g. "gpt56_terra_high") to the `-c key=value`
33
44
  * config overrides that `codex exec` accepts, so the headless CLI lanes can
34
45
  * select a profile WITHOUT `codex exec --profile <name>`. codex 0.134+ rejects
35
46
  * `--profile X` whenever config.toml still contains an inline `[profiles.X]`
@@ -47,10 +58,12 @@ function readProfileScalar(raw, key) {
47
58
  *
48
59
  * @param {string} profileName
49
60
  * @param {{ codexHome?: string }} [opts]
50
- * @returns {string[]} e.g. ['model="gpt-5.5"', 'model_reasoning_effort="high"']
61
+ * @returns {string[]} e.g. ['model="gpt-5.6-terra"', 'model_reasoning_effort="high"']
51
62
  */
52
- export function codexProfileConfigOverrides(profileName, opts = {}) {
53
- if (typeof profileName !== "string" || !profileName) return [];
63
+ export function resolveCodexProfileConfigValues(profileName, opts = {}) {
64
+ if (typeof profileName !== "string" || !profileName) {
65
+ return { model: null, effort: null };
66
+ }
54
67
  const codexHome =
55
68
  opts.codexHome || process.env.CODEX_HOME || join(homedir(), ".codex");
56
69
  let model = null;
@@ -63,9 +76,22 @@ export function codexProfileConfigOverrides(profileName, opts = {}) {
63
76
  model = readProfileScalar(raw, "model");
64
77
  effort = readProfileScalar(raw, "model_reasoning_effort");
65
78
  } catch {
66
- const suffix = /_(xhigh|high|med|medium|low)$/.exec(profileName);
79
+ const suffix = /_(ultra|max|xhigh|high|med|medium|low)$/.exec(profileName);
67
80
  if (suffix) effort = EFFORT_BY_SUFFIX[suffix[1]] || null;
68
81
  }
82
+ return { model, effort };
83
+ }
84
+
85
+ export function codexProfileConfigOverrides(profileName, opts = {}) {
86
+ let { model, effort } = resolveCodexProfileConfigValues(profileName, opts);
87
+ if (opts.enforceCanonicalProfile === true) {
88
+ const canonical = CANONICAL_PROFILE_VALUES[profileName];
89
+ if (canonical) ({ model, effort } = canonical);
90
+ }
91
+ if (opts.disallowUltra === true && effort?.toLowerCase() === "ultra") {
92
+ model = "gpt-5.6-sol";
93
+ effort = "max";
94
+ }
69
95
  const overrides = [];
70
96
  if (model) overrides.push(`model="${model}"`);
71
97
  if (effort) overrides.push(`model_reasoning_effort="${effort}"`);
@@ -26,6 +26,7 @@ function normalizeState(state) {
26
26
 
27
27
  function normalizeRule(rule) {
28
28
  if (!rule || typeof rule !== "object") return null;
29
+ if (rule.disabled === true) return null;
29
30
  if (typeof rule.id !== "string" || !rule.id.trim()) return null;
30
31
  if (!Array.isArray(rule.patterns) || rule.patterns.length === 0) return null;
31
32
  if (typeof rule.priority !== "number" || !Number.isFinite(rule.priority))
@@ -61,6 +62,13 @@ function normalizeRule(rule) {
61
62
  const state = normalizeState(rule.state);
62
63
  if (rule.state != null && state == null) return null;
63
64
 
65
+ // repo_scope: path segment 화이트리스트 (빈 배열 = 전역). explicit: 종결 라우팅 마커.
66
+ const repoScope = Array.isArray(rule.repo_scope)
67
+ ? rule.repo_scope
68
+ .filter((s) => typeof s === "string" && s.trim())
69
+ .map((s) => s.trim())
70
+ : [];
71
+
64
72
  return {
65
73
  id: rule.id.trim(),
66
74
  patterns,
@@ -72,6 +80,8 @@ function normalizeRule(rule) {
72
80
  exclusive: rule.exclusive === true,
73
81
  state,
74
82
  mcp_route: mcpRoute,
83
+ repo_scope: repoScope,
84
+ explicit: rule.explicit === true,
75
85
  };
76
86
  }
77
87
 
@@ -149,6 +159,8 @@ export function matchRules(compiledRules, cleanText) {
149
159
  exclusive: rule.exclusive === true,
150
160
  state: rule.state || null,
151
161
  mcp_route: rule.mcp_route || null,
162
+ repo_scope: rule.repo_scope || [],
163
+ explicit: rule.explicit === true,
152
164
  });
153
165
  }
154
166