triflux 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/README.md +2 -0
- package/adapters/codex/skills/tfx-harness/SKILL.md +30 -0
- package/adapters/codex/skills/tfx-harness/skill.json +5 -0
- package/bin/triflux.mjs +14 -0
- 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/schema.sql +61 -2
- package/hub/server.mjs +42 -0
- package/hub/store.mjs +577 -5
- package/hub/team/conductor.mjs +23 -2
- package/hub/team/execution-mode.mjs +12 -2
- package/hub/team/headless.mjs +16 -0
- package/hub/team/retry-state-machine.mjs +8 -3
- package/hub/team/swarm-hypervisor.mjs +1 -0
- package/hub/team/swarm-preflight.mjs +47 -0
- package/hub/team/synapse-http.mjs +149 -4
- package/hub/team/worktree-lifecycle.mjs +56 -59
- package/hub/tools.mjs +11 -0
- package/hub/workers/codex-mcp.mjs +20 -4
- package/hub/workers/delegator-mcp.mjs +3 -49
- package/package.json +2 -1
- package/scripts/__tests__/lint-skills.test.mjs +68 -0
- package/scripts/__tests__/tfx-route-node-entry.test.mjs +36 -0
- package/scripts/keyword-detector.mjs +55 -8
- 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/scripts/lint-skills.mjs +65 -0
- package/scripts/pack.mjs +13 -0
- package/scripts/release/check-packages-mirror.mjs +5 -0
- package/scripts/setup.mjs +94 -2
- package/scripts/tfx-route.mjs +14 -1
- package/scripts/tfx-route.sh +233 -70
- package/skills/tfx-harness/SKILL.md +19 -76
- package/skills/tfx-hub/SKILL.md +1 -1
- package/skills/tfx-interview/SKILL.md +3 -1
- package/skills/tfx-interview/skill.json +1 -13
- package/skills/tfx-profile/SKILL.md +25 -15
- package/skills/tfx-prune/SKILL.md +3 -1
- package/skills/tfx-prune/skill.json +1 -8
- package/skills/tfx-setup/SKILL.md +1 -1
- package/tui/codex-profile.mjs +11 -2
- package/tui/setup.mjs +2 -0
|
@@ -11,6 +11,7 @@ import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
11
11
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
12
12
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
13
13
|
import * as z from "zod";
|
|
14
|
+
import { resolveCodexAgentPolicy } from "../../scripts/lib/agent-route-policy.mjs";
|
|
14
15
|
import { resolveBashExecutable } from "../lib/bash-path.mjs";
|
|
15
16
|
import { runHeadlessWithCleanup } from "../team/headless.mjs";
|
|
16
17
|
import { buildWorkerSandboxEnv } from "../team/worker-sandbox.mjs";
|
|
@@ -33,57 +34,11 @@ const { buildPromptHint, getCodexMcpConfig, SUPPORTED_MCP_PROFILES } =
|
|
|
33
34
|
await import(pathToFileURL(mcpFilterPath).href);
|
|
34
35
|
const SERVER_INFO = { name: "triflux-delegator", version: "1.0.0" };
|
|
35
36
|
const DEFAULT_CONTEXT_BYTES = 32 * 1024;
|
|
36
|
-
const DEFAULT_ROUTE_TIMEOUT_SEC = 120;
|
|
37
37
|
const DIRECT_PROGRESS_START = 5;
|
|
38
38
|
const DIRECT_PROGRESS_RUNNING = 60;
|
|
39
39
|
const DIRECT_PROGRESS_DONE = 100;
|
|
40
40
|
const SEARCH_ENGINE_CACHE_PATH = [".omc", "state", "search-engines.json"];
|
|
41
41
|
|
|
42
|
-
const AGENT_TIMEOUT_SEC = Object.freeze({
|
|
43
|
-
executor: 1080,
|
|
44
|
-
"build-fixer": 540,
|
|
45
|
-
debugger: 900,
|
|
46
|
-
"deep-executor": 3600,
|
|
47
|
-
architect: 3600,
|
|
48
|
-
planner: 3600,
|
|
49
|
-
critic: 3600,
|
|
50
|
-
analyst: 3600,
|
|
51
|
-
"code-reviewer": 1800,
|
|
52
|
-
"security-reviewer": 1800,
|
|
53
|
-
"quality-reviewer": 1800,
|
|
54
|
-
scientist: 1440,
|
|
55
|
-
"scientist-deep": 3600,
|
|
56
|
-
"document-specialist": 1440,
|
|
57
|
-
designer: 900,
|
|
58
|
-
writer: 900,
|
|
59
|
-
explore: 300,
|
|
60
|
-
verifier: 1200,
|
|
61
|
-
"test-engineer": 300,
|
|
62
|
-
"qa-tester": 300,
|
|
63
|
-
spark: 180,
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
const CODEX_PROFILE_BY_AGENT = Object.freeze({
|
|
67
|
-
executor: "gpt56_terra_high",
|
|
68
|
-
"build-fixer": "gpt56_luna_low",
|
|
69
|
-
debugger: "gpt56_sol_xhigh",
|
|
70
|
-
"deep-executor": "gpt56_sol_xhigh",
|
|
71
|
-
architect: "gpt56_sol_xhigh",
|
|
72
|
-
planner: "gpt56_sol_xhigh",
|
|
73
|
-
critic: "gpt56_sol_xhigh",
|
|
74
|
-
analyst: "gpt56_sol_xhigh",
|
|
75
|
-
"code-reviewer": "gpt56_terra_high",
|
|
76
|
-
"security-reviewer": "gpt56_sol_xhigh",
|
|
77
|
-
"quality-reviewer": "gpt56_terra_high",
|
|
78
|
-
scientist: "gpt56_terra_high",
|
|
79
|
-
"scientist-deep": "gpt56_sol_xhigh",
|
|
80
|
-
"document-specialist": "gpt56_terra_high",
|
|
81
|
-
verifier: "gpt56_terra_high",
|
|
82
|
-
designer: "gpt56_sol_xhigh",
|
|
83
|
-
writer: "gpt56_luna_low",
|
|
84
|
-
spark: "gpt56_luna_low",
|
|
85
|
-
});
|
|
86
|
-
|
|
87
42
|
const REVIEW_INSTRUCTION_BY_AGENT = Object.freeze({
|
|
88
43
|
"code-reviewer":
|
|
89
44
|
"코드 리뷰 모드로 동작하라. 버그, 리스크, 회귀, 테스트 누락을 우선 식별하라.",
|
|
@@ -137,7 +92,7 @@ function resolveRouteScript(explicitPath, cwd = process.cwd()) {
|
|
|
137
92
|
}
|
|
138
93
|
|
|
139
94
|
function resolveCodexProfile(agentType) {
|
|
140
|
-
return
|
|
95
|
+
return resolveCodexAgentPolicy(agentType).profile;
|
|
141
96
|
}
|
|
142
97
|
|
|
143
98
|
function normalizeProvider(provider = "auto") {
|
|
@@ -154,8 +109,7 @@ function resolveTimeoutMs(agentType, timeoutMs) {
|
|
|
154
109
|
return Math.trunc(timeoutMs);
|
|
155
110
|
}
|
|
156
111
|
|
|
157
|
-
|
|
158
|
-
return timeoutSec * 1000;
|
|
112
|
+
return resolveCodexAgentPolicy(agentType).timeoutSec * 1000;
|
|
159
113
|
}
|
|
160
114
|
|
|
161
115
|
function resolveTimeoutSec(agentType, timeoutMs) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "triflux",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.44.0",
|
|
4
4
|
"description": "CLI-first multi-model orchestrator for Claude Code — route tasks to Codex, Antigravity, and Claude",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"playwright-core": "^1.53.0"
|
|
29
29
|
},
|
|
30
30
|
"files": [
|
|
31
|
+
"adapters",
|
|
31
32
|
"bin",
|
|
32
33
|
"tui",
|
|
33
34
|
"config",
|
|
@@ -98,4 +98,72 @@ describe("lint-skills", () => {
|
|
|
98
98
|
rmSync(root, { recursive: true, force: true });
|
|
99
99
|
}
|
|
100
100
|
});
|
|
101
|
+
|
|
102
|
+
it("실행형 Agent 호출은 model=이 없으면 실패한다", () => {
|
|
103
|
+
const root = makeTempDir();
|
|
104
|
+
try {
|
|
105
|
+
const skillsDir = join(root, "skills");
|
|
106
|
+
writeSkill(
|
|
107
|
+
root,
|
|
108
|
+
"tfx-agent",
|
|
109
|
+
'---\nname: tfx-agent\ndescription: 한국어 설명\n---\nAgent(subagent_type="explore", prompt="x")',
|
|
110
|
+
);
|
|
111
|
+
const result = lintSkills({ skillsDir });
|
|
112
|
+
assert.equal(result.ok, false);
|
|
113
|
+
assert.equal(result.problems[0]?.code, "agent-model-required");
|
|
114
|
+
} finally {
|
|
115
|
+
rmSync(root, { recursive: true, force: true });
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("model=이 있는 실행형 Agent와 prose signature는 통과한다", () => {
|
|
120
|
+
const root = makeTempDir();
|
|
121
|
+
try {
|
|
122
|
+
const skillsDir = join(root, "skills");
|
|
123
|
+
writeSkill(
|
|
124
|
+
root,
|
|
125
|
+
"tfx-agent-ok",
|
|
126
|
+
'---\nname: tfx-agent-ok\ndescription: 한국어 설명\n---\nAgent()\nAgent(subagent_type="explore", model="haiku", prompt="x")',
|
|
127
|
+
);
|
|
128
|
+
const result = lintSkills({ skillsDir });
|
|
129
|
+
assert.equal(result.ok, true);
|
|
130
|
+
} finally {
|
|
131
|
+
rmSync(root, { recursive: true, force: true });
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("문자열 안의 괄호가 있어도 model=이 있는 실행형 Agent를 통과시킨다", () => {
|
|
136
|
+
const root = makeTempDir();
|
|
137
|
+
try {
|
|
138
|
+
const skillsDir = join(root, "skills");
|
|
139
|
+
writeSkill(
|
|
140
|
+
root,
|
|
141
|
+
"tfx-agent-nested-model",
|
|
142
|
+
'---\nname: tfx-agent-nested-model\ndescription: 한국어 설명\n---\nAgent(subagent_type="x", prompt="do (this)", model="haiku")',
|
|
143
|
+
);
|
|
144
|
+
const result = lintSkills({ skillsDir });
|
|
145
|
+
assert.equal(result.ok, true);
|
|
146
|
+
assert.deepEqual(result.problems, []);
|
|
147
|
+
} finally {
|
|
148
|
+
rmSync(root, { recursive: true, force: true });
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("문자열 안의 괄호가 있어도 model= 없는 실행형 Agent를 실패 처리한다", () => {
|
|
153
|
+
const root = makeTempDir();
|
|
154
|
+
try {
|
|
155
|
+
const skillsDir = join(root, "skills");
|
|
156
|
+
writeSkill(
|
|
157
|
+
root,
|
|
158
|
+
"tfx-agent-nested-missing-model",
|
|
159
|
+
'---\nname: tfx-agent-nested-missing-model\ndescription: 한국어 설명\n---\nAgent(prompt="foo(bar)", subagent_type="x")',
|
|
160
|
+
);
|
|
161
|
+
const result = lintSkills({ skillsDir });
|
|
162
|
+
assert.equal(result.ok, false);
|
|
163
|
+
assert.equal(result.problems.length, 1);
|
|
164
|
+
assert.equal(result.problems[0]?.code, "agent-model-required");
|
|
165
|
+
} finally {
|
|
166
|
+
rmSync(root, { recursive: true, force: true });
|
|
167
|
+
}
|
|
168
|
+
});
|
|
101
169
|
});
|
|
@@ -262,6 +262,42 @@ describe("cli-codex.mjs — 핵심 매핑", () => {
|
|
|
262
262
|
assert.equal(p.mcpProfile, "analyze");
|
|
263
263
|
});
|
|
264
264
|
|
|
265
|
+
test("explicit max override selects the canonical Sol max profile", () => {
|
|
266
|
+
const p = codexAdapter.plan({
|
|
267
|
+
agent: "architect",
|
|
268
|
+
prompt: "x",
|
|
269
|
+
mcpProfile: "auto",
|
|
270
|
+
profileOverride: "max",
|
|
271
|
+
});
|
|
272
|
+
assert.equal(p.profile, "gpt56_sol_max");
|
|
273
|
+
assert.equal(p.effort, "gpt56_sol_max");
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test("ultra is limited to top-level deep execution and research", () => {
|
|
277
|
+
const topLevel = codexAdapter.plan({
|
|
278
|
+
agent: "deep-executor",
|
|
279
|
+
prompt: "x",
|
|
280
|
+
profileOverride: "ultra",
|
|
281
|
+
nested: false,
|
|
282
|
+
});
|
|
283
|
+
const nested = codexAdapter.plan({
|
|
284
|
+
agent: "deep-executor",
|
|
285
|
+
prompt: "x",
|
|
286
|
+
profileOverride: "ultra",
|
|
287
|
+
nested: true,
|
|
288
|
+
});
|
|
289
|
+
const ineligible = codexAdapter.plan({
|
|
290
|
+
agent: "architect",
|
|
291
|
+
prompt: "x",
|
|
292
|
+
profileOverride: "ultra",
|
|
293
|
+
nested: false,
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
assert.equal(topLevel.profile, "gpt56_sol_ultra");
|
|
297
|
+
assert.equal(nested.profile, "gpt56_sol_max");
|
|
298
|
+
assert.equal(ineligible.profile, "gpt56_sol_max");
|
|
299
|
+
});
|
|
300
|
+
|
|
265
301
|
test("security-reviewer → review 서브커맨드 + opus oversight", () => {
|
|
266
302
|
const p = codexAdapter.plan({
|
|
267
303
|
agent: "security-reviewer",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
|
-
import { dirname, join } from "node:path";
|
|
3
|
+
import { mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import {
|
|
7
7
|
compileRules,
|
|
@@ -90,6 +90,27 @@ export function sanitizeForKeywordDetection(text) {
|
|
|
90
90
|
.trim();
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
// repo_scope 는 path segment 단위로 매칭한다 — "triflux" 는 .../tools/triflux/... 에만
|
|
94
|
+
// 매칭되고 .../triflux-ideas 같은 부분 문자열에는 매칭되지 않는다. 빈 scope = 전역.
|
|
95
|
+
export function matchesRepoScope(path, scopes) {
|
|
96
|
+
if (!Array.isArray(scopes) || scopes.length === 0) return true;
|
|
97
|
+
if (typeof path !== "string" || !path) return false;
|
|
98
|
+
const segments = path.split(/[\\/]+/).filter(Boolean);
|
|
99
|
+
return scopes.some((scope) => segments.includes(scope));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// resolvedMatches[0] 단일 승자의 우선순위 역전 방지 — explicit(종결 라우팅) 규칙이
|
|
103
|
+
// 있으면 정렬 순서와 무관하게 그 규칙이 이긴다. 없으면 정렬 선두 유지.
|
|
104
|
+
export function selectPrimaryMatch(resolvedMatches) {
|
|
105
|
+
if (!Array.isArray(resolvedMatches) || resolvedMatches.length === 0) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
const explicitMatch = resolvedMatches.find(
|
|
109
|
+
(match) => match?.explicit === true,
|
|
110
|
+
);
|
|
111
|
+
return explicitMatch ?? resolvedMatches[0];
|
|
112
|
+
}
|
|
113
|
+
|
|
93
114
|
function createHookOutput(additionalContext) {
|
|
94
115
|
return {
|
|
95
116
|
continue: true,
|
|
@@ -263,7 +284,6 @@ function main() {
|
|
|
263
284
|
return;
|
|
264
285
|
}
|
|
265
286
|
|
|
266
|
-
const selected = resolvedMatches[0];
|
|
267
287
|
const baseDir =
|
|
268
288
|
typeof payload.cwd === "string" && payload.cwd
|
|
269
289
|
? payload.cwd
|
|
@@ -271,6 +291,15 @@ function main() {
|
|
|
271
291
|
? payload.directory
|
|
272
292
|
: process.cwd();
|
|
273
293
|
|
|
294
|
+
const scopedMatches = resolvedMatches.filter((match) =>
|
|
295
|
+
matchesRepoScope(baseDir, match.repo_scope),
|
|
296
|
+
);
|
|
297
|
+
const selected = selectPrimaryMatch(scopedMatches);
|
|
298
|
+
if (!selected) {
|
|
299
|
+
console.log(JSON.stringify(createSuppressOutput()));
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
|
|
274
303
|
activateState(baseDir, selected.state, prompt, payload);
|
|
275
304
|
|
|
276
305
|
if (selected.action === "context_hint" && selected.hint) {
|
|
@@ -308,9 +337,27 @@ function main() {
|
|
|
308
337
|
console.log(JSON.stringify(createSuppressOutput()));
|
|
309
338
|
}
|
|
310
339
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
340
|
+
// 훅 실행(node scripts/keyword-detector.mjs)일 때만 main 구동 — 테스트 import 시
|
|
341
|
+
// stdin 대기로 행이 걸리지 않도록 direct-run 가드.
|
|
342
|
+
const isDirectRun = (() => {
|
|
343
|
+
try {
|
|
344
|
+
if (typeof process.argv[1] !== "string" || !process.argv[1]) return false;
|
|
345
|
+
const selfPath = fileURLToPath(import.meta.url);
|
|
346
|
+
const argvPath = resolve(process.argv[1]);
|
|
347
|
+
if (selfPath === argvPath) return true;
|
|
348
|
+
// 심링크 설치 경로: argv[1]이 심링크면 import.meta.url(실경로)과 어긋난다.
|
|
349
|
+
// canonical 경로로 재비교 (realpath 실패 시 non-direct 취급).
|
|
350
|
+
return selfPath === realpathSync(argvPath);
|
|
351
|
+
} catch {
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
354
|
+
})();
|
|
355
|
+
|
|
356
|
+
if (isDirectRun) {
|
|
357
|
+
try {
|
|
358
|
+
main();
|
|
359
|
+
} catch (error) {
|
|
360
|
+
console.error(`[triflux-keyword-detector] 예외 발생: ${error.message}`);
|
|
361
|
+
console.log(JSON.stringify(createSuppressOutput()));
|
|
362
|
+
}
|
|
316
363
|
}
|
|
@@ -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
|
+
}
|