@triflux/remote 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/cto/hygiene.mjs +1 -1
- package/hub/lib/cto-env.mjs +1 -0
- package/hub/pipe.mjs +7 -1
- package/hub/schema.sql +61 -2
- package/hub/server.mjs +104 -39
- package/hub/store.mjs +601 -9
- package/hub/team/conductor.mjs +23 -2
- package/hub/team/execution-mode.mjs +12 -7
- package/hub/team/headless.mjs +51 -15
- package/hub/team/intervention.mjs +1 -1
- package/hub/team/notify.mjs +31 -11
- package/hub/team/retry-state-machine.mjs +8 -3
- package/hub/team/swarm-hypervisor.mjs +4 -2
- package/hub/team/swarm-preflight.mjs +47 -0
- package/hub/team/worktree-lifecycle.mjs +56 -59
- package/hub/tools.mjs +13 -1
- package/hub/workers/codex-mcp.mjs +20 -4
- package/hub/workers/delegator-mcp.mjs +3 -49
- package/package.json +1 -1
- package/scripts/lib/agent-route-policy.mjs +307 -0
- package/scripts/lib/cli-codex.mjs +20 -160
- package/scripts/lib/codex-profile-config.mjs +29 -3
- package/scripts/lib/keyword-rules.mjs +12 -0
- package/cto/steward.mjs +0 -362
- package/hub/log-retention.mjs +0 -726
|
@@ -4,8 +4,16 @@
|
|
|
4
4
|
// Remote support: host option → SSH-based git operations via remote-session.mjs.
|
|
5
5
|
|
|
6
6
|
import { execFile } from "node:child_process";
|
|
7
|
-
import {
|
|
8
|
-
|
|
7
|
+
import {
|
|
8
|
+
access,
|
|
9
|
+
mkdir,
|
|
10
|
+
mkdtemp,
|
|
11
|
+
readdir,
|
|
12
|
+
realpath,
|
|
13
|
+
rm,
|
|
14
|
+
} from "node:fs/promises";
|
|
15
|
+
import { tmpdir } from "node:os";
|
|
16
|
+
import { join, normalize, relative, resolve } from "node:path";
|
|
9
17
|
import { remoteGit, validateHost } from "./remote-session.mjs";
|
|
10
18
|
|
|
11
19
|
const SWARM_ROOT = ".codex-swarm";
|
|
@@ -327,24 +335,23 @@ export async function rebaseShardOntoIntegration({
|
|
|
327
335
|
}
|
|
328
336
|
}
|
|
329
337
|
|
|
330
|
-
// #127 BUG-E:
|
|
331
|
-
//
|
|
332
|
-
//
|
|
333
|
-
//
|
|
334
|
-
//
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
} catch {
|
|
340
|
-
/* detached HEAD or unknown — skip restore */
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// Backup integration HEAD for rollback
|
|
338
|
+
// #127 BUG-E accident history: this function used to checkout the
|
|
339
|
+
// integration branch in rootDir, leak rootDir HEAD, and redirect later user
|
|
340
|
+
// edits/commits into swarm/.../merge. Integration now happens exclusively in
|
|
341
|
+
// a disposable worktree, so rootDir HEAD never moves and needs no restore.
|
|
342
|
+
//
|
|
343
|
+
// #129 BUG-J accident history: checkout failure was followed by a blind
|
|
344
|
+
// reset --hard (or branch -f rollback) against rootDir, which rewound the
|
|
345
|
+
// caller branch. Rollback now resets only the disposable worktree; there is
|
|
346
|
+
// no rootDir reset path and therefore no stash-create last-resort path.
|
|
344
347
|
const backupCommit = await git(["rev-parse", integrationBranch], rootDir);
|
|
345
348
|
|
|
346
349
|
let shaList = [];
|
|
347
350
|
let ffError = null;
|
|
351
|
+
let integrationWorktreeAdded = false;
|
|
352
|
+
const integrationWorktree = await mkdtemp(
|
|
353
|
+
join(tmpdir(), "tfx-swarm-integration-"),
|
|
354
|
+
);
|
|
348
355
|
|
|
349
356
|
try {
|
|
350
357
|
const log = await git(
|
|
@@ -361,11 +368,15 @@ export async function rebaseShardOntoIntegration({
|
|
|
361
368
|
.map((s) => s.trim())
|
|
362
369
|
.filter(Boolean);
|
|
363
370
|
|
|
364
|
-
await git(
|
|
371
|
+
await git(
|
|
372
|
+
["worktree", "add", "--quiet", integrationWorktree, integrationBranch],
|
|
373
|
+
rootDir,
|
|
374
|
+
);
|
|
375
|
+
integrationWorktreeAdded = true;
|
|
365
376
|
|
|
366
377
|
try {
|
|
367
|
-
await git(["merge", "--ff-only", shardBranch],
|
|
368
|
-
const headCommit = await git(["rev-parse", "HEAD"],
|
|
378
|
+
await git(["merge", "--ff-only", shardBranch], integrationWorktree);
|
|
379
|
+
const headCommit = await git(["rev-parse", "HEAD"], integrationWorktree);
|
|
369
380
|
return {
|
|
370
381
|
ok: true,
|
|
371
382
|
headCommit,
|
|
@@ -376,12 +387,15 @@ export async function rebaseShardOntoIntegration({
|
|
|
376
387
|
} catch (err) {
|
|
377
388
|
ffError = err;
|
|
378
389
|
try {
|
|
379
|
-
await git(["merge", "--abort"],
|
|
390
|
+
await git(["merge", "--abort"], integrationWorktree);
|
|
380
391
|
} catch {
|
|
381
392
|
/* ff-only usually leaves no merge state */
|
|
382
393
|
}
|
|
383
394
|
try {
|
|
384
|
-
|
|
395
|
+
// Safe destructive operation: this cwd is the disposable integration
|
|
396
|
+
// worktree, never rootDir. It restores the integration ref before the
|
|
397
|
+
// cherry-pick fallback without touching user files.
|
|
398
|
+
await git(["reset", "--hard", backupCommit], integrationWorktree);
|
|
385
399
|
} catch {
|
|
386
400
|
/* fallback below will report if cherry-pick also fails */
|
|
387
401
|
}
|
|
@@ -391,10 +405,10 @@ export async function rebaseShardOntoIntegration({
|
|
|
391
405
|
// fail when histories diverge or a shard branch is checked out by a swarm
|
|
392
406
|
// worktree. Cherry-pick reads commits without moving the shard branch ref.
|
|
393
407
|
for (const sha of shaList) {
|
|
394
|
-
await git(["cherry-pick", sha],
|
|
408
|
+
await git(["cherry-pick", sha], integrationWorktree);
|
|
395
409
|
}
|
|
396
410
|
|
|
397
|
-
const headCommit = await git(["rev-parse", "HEAD"],
|
|
411
|
+
const headCommit = await git(["rev-parse", "HEAD"], integrationWorktree);
|
|
398
412
|
return {
|
|
399
413
|
ok: true,
|
|
400
414
|
headCommit,
|
|
@@ -406,42 +420,17 @@ export async function rebaseShardOntoIntegration({
|
|
|
406
420
|
: "cherry-pick fallback",
|
|
407
421
|
};
|
|
408
422
|
} catch (err) {
|
|
409
|
-
|
|
410
|
-
await git(["cherry-pick", "--abort"], rootDir);
|
|
411
|
-
} catch {
|
|
412
|
-
/* already clean */
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
// #129 BUG-J: Roll back integrationBranch without mutating whatever
|
|
416
|
-
// branch HEAD currently points at. The previous implementation did a
|
|
417
|
-
// blind `reset --hard backupCommit` on current HEAD — if the earlier
|
|
418
|
-
// `git checkout integrationBranch` inside the try block failed (e.g.
|
|
419
|
-
// integrationBranch was already checked out by a swarm worktree) the
|
|
420
|
-
// caller's branch silently rewound to integrationBranch's backup commit.
|
|
421
|
-
// Observed 2026-04-20: fix branch tree lost to main HEAD during swarm
|
|
422
|
-
// probe, requiring `git merge --ff-only origin/<branch>` recovery.
|
|
423
|
-
let currentBranch = null;
|
|
424
|
-
try {
|
|
425
|
-
const head = await git(["rev-parse", "--abbrev-ref", "HEAD"], rootDir);
|
|
426
|
-
if (head && head !== "HEAD") currentBranch = head;
|
|
427
|
-
} catch {
|
|
428
|
-
/* detached — fall through to ref-only update */
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
if (currentBranch === integrationBranch) {
|
|
432
|
-
// HEAD is on integrationBranch — reset advances this branch only.
|
|
423
|
+
if (integrationWorktreeAdded) {
|
|
433
424
|
try {
|
|
434
|
-
await git(["
|
|
425
|
+
await git(["cherry-pick", "--abort"], integrationWorktree);
|
|
435
426
|
} catch {
|
|
436
|
-
/*
|
|
427
|
+
/* already clean */
|
|
437
428
|
}
|
|
438
|
-
} else {
|
|
439
|
-
// HEAD is elsewhere (originalBranch, detached, or another branch).
|
|
440
|
-
// Update the integrationBranch ref directly; never touch current HEAD.
|
|
441
429
|
try {
|
|
442
|
-
|
|
430
|
+
// #129 rollback is now local to the disposable integration worktree.
|
|
431
|
+
await git(["reset", "--hard", backupCommit], integrationWorktree);
|
|
443
432
|
} catch {
|
|
444
|
-
/* best-effort
|
|
433
|
+
/* best-effort */
|
|
445
434
|
}
|
|
446
435
|
}
|
|
447
436
|
|
|
@@ -453,14 +442,22 @@ export async function rebaseShardOntoIntegration({
|
|
|
453
442
|
fallbackReason: ffError?.message || null,
|
|
454
443
|
};
|
|
455
444
|
} finally {
|
|
456
|
-
|
|
457
|
-
// (detached HEAD case) or already on target.
|
|
458
|
-
if (originalBranch) {
|
|
445
|
+
if (integrationWorktreeAdded) {
|
|
459
446
|
try {
|
|
460
|
-
await git(
|
|
447
|
+
await git(
|
|
448
|
+
["worktree", "remove", "--force", integrationWorktree],
|
|
449
|
+
rootDir,
|
|
450
|
+
);
|
|
461
451
|
} catch {
|
|
462
|
-
|
|
452
|
+
await rm(integrationWorktree, { recursive: true, force: true });
|
|
453
|
+
try {
|
|
454
|
+
await git(["worktree", "prune"], rootDir);
|
|
455
|
+
} catch {
|
|
456
|
+
/* best-effort metadata cleanup */
|
|
457
|
+
}
|
|
463
458
|
}
|
|
459
|
+
} else {
|
|
460
|
+
await rm(integrationWorktree, { recursive: true, force: true });
|
|
464
461
|
}
|
|
465
462
|
}
|
|
466
463
|
}
|
package/hub/tools.mjs
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
// register/status/publish/ask/poll/handoff/HITL + team proxy
|
|
3
3
|
// 모든 도구 응답: { ok: boolean, error?: { code, message }, data?: ... }
|
|
4
4
|
|
|
5
|
+
import {
|
|
6
|
+
DEFAULT_ASSIGN_TIMEOUT_MS,
|
|
7
|
+
DEFAULT_ASSIGN_TTL_MS,
|
|
8
|
+
DEFAULT_HANDOFF_TTL_MS,
|
|
9
|
+
} from "@triflux/core/hub/lib/timeout-defaults.mjs";
|
|
5
10
|
import { createPipeline, ensurePipelineTable } from "@triflux/core/hub/pipeline/index.mjs";
|
|
6
11
|
import {
|
|
7
12
|
initPipelineState,
|
|
@@ -10,7 +15,6 @@ import {
|
|
|
10
15
|
} from "@triflux/core/hub/pipeline/state.mjs";
|
|
11
16
|
import { sendInputToConductorSession } from "./team/conductor-registry.mjs";
|
|
12
17
|
import { getTeamBridge } from "@triflux/core/hub/team-bridge.mjs";
|
|
13
|
-
import { DEFAULT_ASSIGN_TIMEOUT_MS, DEFAULT_ASSIGN_TTL_MS, DEFAULT_HANDOFF_TTL_MS } from "@triflux/core/hub/lib/timeout-defaults.mjs";
|
|
14
18
|
|
|
15
19
|
const TEAM_BRIDGE_NOT_REGISTERED = "bridge_not_registered";
|
|
16
20
|
|
|
@@ -149,6 +153,14 @@ export function createTools(store, router, hitl, pipe = null) {
|
|
|
149
153
|
},
|
|
150
154
|
topics: { type: "array", items: { type: "string" }, maxItems: 64 },
|
|
151
155
|
metadata: { type: "object" },
|
|
156
|
+
project_id: { type: "string", pattern: "^prj_[A-Za-z0-9_-]{22}$" },
|
|
157
|
+
session_id: { type: "string", minLength: 1, maxLength: 256 },
|
|
158
|
+
host_id: { type: "string", pattern: "^hst_[A-Za-z0-9_-]+$" },
|
|
159
|
+
transport_locators_json: {
|
|
160
|
+
description:
|
|
161
|
+
"tfx.transport-locator.v1 객체 배열 또는 그 JSON 문자열",
|
|
162
|
+
oneOf: [{ type: "array", items: { type: "object" } }, { type: "string" }],
|
|
163
|
+
},
|
|
152
164
|
heartbeat_ttl_ms: {
|
|
153
165
|
type: "integer",
|
|
154
166
|
minimum: 10000,
|
|
@@ -146,9 +146,11 @@ export function resolveCodexProfileConfig(profileName) {
|
|
|
146
146
|
} catch {
|
|
147
147
|
// Profile file absent (non-file alias). Derive effort from the naming
|
|
148
148
|
// convention; leave model unset so codex uses its config.toml default.
|
|
149
|
-
const suffix = /_(xhigh|high|med|medium|low)$/.exec(profileName);
|
|
149
|
+
const suffix = /_(ultra|max|xhigh|high|med|medium|low)$/.exec(profileName);
|
|
150
150
|
if (suffix) {
|
|
151
151
|
const map = {
|
|
152
|
+
ultra: "ultra",
|
|
153
|
+
max: "max",
|
|
152
154
|
xhigh: "xhigh",
|
|
153
155
|
high: "high",
|
|
154
156
|
med: "medium",
|
|
@@ -193,16 +195,27 @@ export function buildCodexArguments(prompt, opts = {}) {
|
|
|
193
195
|
// profile effort merges on top of any opts.config.
|
|
194
196
|
if (typeof opts.profile === "string" && opts.profile) {
|
|
195
197
|
const resolved = resolveCodexProfileConfig(opts.profile);
|
|
196
|
-
|
|
197
|
-
|
|
198
|
+
const profileDerivedUltra =
|
|
199
|
+
resolved.reasoningEffort?.toLowerCase() === "ultra";
|
|
200
|
+
if (typeof args.model !== "string") {
|
|
201
|
+
if (profileDerivedUltra) args.model = "gpt-5.6-sol";
|
|
202
|
+
else if (resolved.model) args.model = resolved.model;
|
|
198
203
|
}
|
|
199
204
|
if (resolved.reasoningEffort) {
|
|
200
205
|
args.config = {
|
|
201
206
|
...(args.config || {}),
|
|
202
|
-
model_reasoning_effort:
|
|
207
|
+
model_reasoning_effort: profileDerivedUltra
|
|
208
|
+
? "max"
|
|
209
|
+
: resolved.reasoningEffort,
|
|
203
210
|
};
|
|
204
211
|
}
|
|
205
212
|
}
|
|
213
|
+
if (typeof opts.reasoningEffort === "string" && opts.reasoningEffort) {
|
|
214
|
+
args.config = {
|
|
215
|
+
...(args.config || {}),
|
|
216
|
+
model_reasoning_effort: opts.reasoningEffort,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
206
219
|
|
|
207
220
|
return args;
|
|
208
221
|
}
|
|
@@ -694,6 +707,9 @@ function parseCliArgs(argv) {
|
|
|
694
707
|
case "--model":
|
|
695
708
|
options.model = next();
|
|
696
709
|
break;
|
|
710
|
+
case "--reasoning-effort":
|
|
711
|
+
options.reasoningEffort = next();
|
|
712
|
+
break;
|
|
697
713
|
case "--approval-policy":
|
|
698
714
|
options.approvalPolicy = next();
|
|
699
715
|
break;
|
|
@@ -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 "@triflux/core/scripts/lib/agent-route-policy.mjs";
|
|
14
15
|
import { resolveBashExecutable } from "@triflux/core/hub/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
|
@@ -0,0 +1,307 @@
|
|
|
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", "gpt56_terra_med", "gpt56_terra_high", "gpt56_sol_xhigh",
|
|
211
|
+
"gpt56_sol_max", "gpt56_sol_ultra",
|
|
212
|
+
]),
|
|
213
|
+
);
|
|
214
|
+
export const ULTRA_ELIGIBLE_CODEX_AGENTS = Object.freeze(
|
|
215
|
+
new Set(["deep-executor", "scientist-deep"]),
|
|
216
|
+
);
|
|
217
|
+
const NESTED_GLOBAL_PROFILE_OVERRIDES = new Set([
|
|
218
|
+
"max", "ultra", "gpt56_sol_max", "gpt56_sol_ultra",
|
|
219
|
+
]);
|
|
220
|
+
|
|
221
|
+
export function resolveCodexAgentPolicy(agent) {
|
|
222
|
+
return freezePolicy(
|
|
223
|
+
CODEX_AGENT_POLICY[agent] ?? CODEX_AGENT_POLICY[DEFAULT_CODEX_AGENT],
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function resolveCodexAgentProfile(
|
|
228
|
+
agent,
|
|
229
|
+
{ profileOverride = "auto", retryProfile = null, nested = false, allowCustom = false } = {},
|
|
230
|
+
) {
|
|
231
|
+
const defaultProfile = resolveCodexAgentPolicy(agent).profile;
|
|
232
|
+
const retryRequested = String(retryProfile ?? "").trim();
|
|
233
|
+
const requested = retryRequested || String(profileOverride || "auto").trim();
|
|
234
|
+
if (!requested || requested === "auto") return defaultProfile;
|
|
235
|
+
const canonical = requested === "max" ? "gpt56_sol_max" : requested === "ultra" ? "gpt56_sol_ultra" : requested;
|
|
236
|
+
if (!CANONICAL_CODEX_PROFILE_OVERRIDES.has(canonical)) {
|
|
237
|
+
if (allowCustom) return requested;
|
|
238
|
+
throw new Error(`[agent-route-policy] unsupported profile override: ${requested}`);
|
|
239
|
+
}
|
|
240
|
+
if (canonical !== "gpt56_sol_ultra") return canonical;
|
|
241
|
+
return nested || !ULTRA_ELIGIBLE_CODEX_AGENTS.has(agent) ? "gpt56_sol_max" : canonical;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export function resolveNestedCodexAgentProfile(
|
|
245
|
+
agent,
|
|
246
|
+
{ profileOverride = "auto", globalProfile = "auto", retryProfile = null, codexHome } = {},
|
|
247
|
+
) {
|
|
248
|
+
const explicitProfile = String(profileOverride || "auto").trim();
|
|
249
|
+
const globalOverride = String(globalProfile || "auto").trim();
|
|
250
|
+
const effectiveOverride = explicitProfile !== "auto" ? explicitProfile : NESTED_GLOBAL_PROFILE_OVERRIDES.has(globalOverride) ? globalOverride : "auto";
|
|
251
|
+
const fallback = resolveCodexAgentProfile(agent);
|
|
252
|
+
const candidate = resolveCodexAgentProfile(agent, { profileOverride: effectiveOverride, retryProfile, nested: true, allowCustom: true });
|
|
253
|
+
const { effort } = resolveCodexProfileConfigValues(candidate, { codexHome });
|
|
254
|
+
if (!effort) return fallback;
|
|
255
|
+
return effort.toLowerCase() === "ultra" ? "gpt56_sol_max" : candidate;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export function describeCodexAgentPolicy() {
|
|
259
|
+
return {
|
|
260
|
+
defaultAgent: DEFAULT_CODEX_AGENT,
|
|
261
|
+
agents: Object.keys(CODEX_AGENT_POLICY),
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function parseCliArgs(argv) {
|
|
266
|
+
const args = [...argv];
|
|
267
|
+
let format = "json";
|
|
268
|
+
let agent = DEFAULT_CODEX_AGENT;
|
|
269
|
+
while (args.length > 0) {
|
|
270
|
+
const flag = args.shift();
|
|
271
|
+
if (flag === "--format") format = args.shift() || "";
|
|
272
|
+
else if (flag === "--agent") agent = args.shift() || "";
|
|
273
|
+
else throw new Error(`unknown argument: ${flag}`);
|
|
274
|
+
}
|
|
275
|
+
if (!new Set(["json", "tsv"]).has(format)) {
|
|
276
|
+
throw new Error(`unsupported format: ${format}`);
|
|
277
|
+
}
|
|
278
|
+
return { format, agent };
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function runCli() {
|
|
282
|
+
const { format, agent } = parseCliArgs(process.argv.slice(2));
|
|
283
|
+
const resolved = resolveCodexAgentPolicy(agent);
|
|
284
|
+
if (format === "tsv") {
|
|
285
|
+
process.stdout.write(
|
|
286
|
+
[
|
|
287
|
+
resolved.profile,
|
|
288
|
+
resolved.timeoutSec,
|
|
289
|
+
resolved.runMode,
|
|
290
|
+
resolved.opusOversight,
|
|
291
|
+
resolved.mcpHint,
|
|
292
|
+
resolved.subcommand,
|
|
293
|
+
].join("\t"),
|
|
294
|
+
);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
process.stdout.write(`${JSON.stringify(resolved)}\n`);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (import.meta.url === new URL(process.argv[1], "file:").href) {
|
|
301
|
+
try {
|
|
302
|
+
runCli();
|
|
303
|
+
} catch (error) {
|
|
304
|
+
process.stderr.write(`[agent-route-policy] ${error.message}\n`);
|
|
305
|
+
process.exitCode = 1;
|
|
306
|
+
}
|
|
307
|
+
}
|