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
|
@@ -4,169 +4,23 @@
|
|
|
4
4
|
// 산출한다. 실제 spawn 은 Phase 1 에서 이관된다 (현재는 tfx-route.sh 가 수행).
|
|
5
5
|
//
|
|
6
6
|
// 진입점: tfx-route.mjs → selectAdapter("codex") → plan({...}).
|
|
7
|
-
// 출처:
|
|
7
|
+
// 출처: agent-route-policy.mjs 의 canonical Codex role policy.
|
|
8
8
|
// timeoutSec은 lane 예상 소요 시간(advisory)이다. hard ceiling 집행은 Bash route가 소유한다.
|
|
9
9
|
|
|
10
10
|
export const id = "codex";
|
|
11
11
|
export const cliType = "codex";
|
|
12
12
|
export const command = "codex";
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
mcpHint: "implement",
|
|
21
|
-
},
|
|
22
|
-
codex: {
|
|
23
|
-
profile: "gpt56_terra_high",
|
|
24
|
-
timeoutSec: 1080,
|
|
25
|
-
runMode: "fg",
|
|
26
|
-
opusOversight: false,
|
|
27
|
-
mcpHint: "implement",
|
|
28
|
-
},
|
|
29
|
-
"build-fixer": {
|
|
30
|
-
profile: "gpt56_luna_low",
|
|
31
|
-
timeoutSec: 540,
|
|
32
|
-
runMode: "fg",
|
|
33
|
-
opusOversight: false,
|
|
34
|
-
mcpHint: "implement",
|
|
35
|
-
},
|
|
36
|
-
cleanup: {
|
|
37
|
-
profile: "gpt56_terra_med",
|
|
38
|
-
timeoutSec: 540,
|
|
39
|
-
runMode: "fg",
|
|
40
|
-
opusOversight: false,
|
|
41
|
-
mcpHint: "implement",
|
|
42
|
-
},
|
|
43
|
-
deslop: {
|
|
44
|
-
profile: "gpt56_terra_med",
|
|
45
|
-
timeoutSec: 540,
|
|
46
|
-
runMode: "fg",
|
|
47
|
-
opusOversight: false,
|
|
48
|
-
mcpHint: "implement",
|
|
49
|
-
},
|
|
50
|
-
debugger: {
|
|
51
|
-
profile: "gpt56_sol_xhigh",
|
|
52
|
-
timeoutSec: 900,
|
|
53
|
-
runMode: "bg",
|
|
54
|
-
opusOversight: false,
|
|
55
|
-
mcpHint: "implement",
|
|
56
|
-
},
|
|
57
|
-
"deep-executor": {
|
|
58
|
-
profile: "gpt56_sol_xhigh",
|
|
59
|
-
timeoutSec: 3600,
|
|
60
|
-
runMode: "bg",
|
|
61
|
-
opusOversight: true,
|
|
62
|
-
mcpHint: "implement",
|
|
63
|
-
},
|
|
64
|
-
architect: {
|
|
65
|
-
profile: "gpt56_sol_xhigh",
|
|
66
|
-
timeoutSec: 3600,
|
|
67
|
-
runMode: "bg",
|
|
68
|
-
opusOversight: true,
|
|
69
|
-
mcpHint: "analyze",
|
|
70
|
-
},
|
|
71
|
-
critic: {
|
|
72
|
-
profile: "gpt56_sol_xhigh",
|
|
73
|
-
timeoutSec: 3600,
|
|
74
|
-
runMode: "bg",
|
|
75
|
-
opusOversight: true,
|
|
76
|
-
mcpHint: "analyze",
|
|
77
|
-
},
|
|
78
|
-
planner: {
|
|
79
|
-
profile: "gpt56_sol_xhigh",
|
|
80
|
-
timeoutSec: 3600,
|
|
81
|
-
runMode: "fg",
|
|
82
|
-
opusOversight: true,
|
|
83
|
-
mcpHint: "analyze",
|
|
84
|
-
},
|
|
85
|
-
analyst: {
|
|
86
|
-
profile: "gpt56_sol_xhigh",
|
|
87
|
-
timeoutSec: 3600,
|
|
88
|
-
runMode: "fg",
|
|
89
|
-
opusOversight: true,
|
|
90
|
-
mcpHint: "analyze",
|
|
91
|
-
},
|
|
92
|
-
"code-reviewer": {
|
|
93
|
-
profile: "gpt56_terra_high",
|
|
94
|
-
timeoutSec: 1800,
|
|
95
|
-
runMode: "bg",
|
|
96
|
-
opusOversight: false,
|
|
97
|
-
mcpHint: "review",
|
|
98
|
-
subcommand: "review",
|
|
99
|
-
},
|
|
100
|
-
"quality-reviewer": {
|
|
101
|
-
profile: "gpt56_terra_high",
|
|
102
|
-
timeoutSec: 1800,
|
|
103
|
-
runMode: "bg",
|
|
104
|
-
opusOversight: false,
|
|
105
|
-
mcpHint: "review",
|
|
106
|
-
subcommand: "review",
|
|
107
|
-
},
|
|
108
|
-
"security-reviewer": {
|
|
109
|
-
profile: "gpt56_sol_xhigh",
|
|
110
|
-
timeoutSec: 1800,
|
|
111
|
-
runMode: "bg",
|
|
112
|
-
opusOversight: true,
|
|
113
|
-
mcpHint: "review",
|
|
114
|
-
subcommand: "review",
|
|
115
|
-
},
|
|
116
|
-
scientist: {
|
|
117
|
-
profile: "gpt56_terra_high",
|
|
118
|
-
timeoutSec: 1440,
|
|
119
|
-
runMode: "bg",
|
|
120
|
-
opusOversight: false,
|
|
121
|
-
mcpHint: "analyze",
|
|
122
|
-
},
|
|
123
|
-
"document-specialist": {
|
|
124
|
-
profile: "gpt56_terra_high",
|
|
125
|
-
timeoutSec: 1440,
|
|
126
|
-
runMode: "bg",
|
|
127
|
-
opusOversight: false,
|
|
128
|
-
mcpHint: "analyze",
|
|
129
|
-
},
|
|
130
|
-
"scientist-deep": {
|
|
131
|
-
profile: "gpt56_sol_xhigh",
|
|
132
|
-
timeoutSec: 3600,
|
|
133
|
-
runMode: "bg",
|
|
134
|
-
opusOversight: false,
|
|
135
|
-
mcpHint: "analyze",
|
|
136
|
-
},
|
|
137
|
-
verifier: {
|
|
138
|
-
profile: "gpt56_terra_high",
|
|
139
|
-
timeoutSec: 1200,
|
|
140
|
-
runMode: "fg",
|
|
141
|
-
opusOversight: false,
|
|
142
|
-
mcpHint: "review",
|
|
143
|
-
subcommand: "review",
|
|
144
|
-
},
|
|
145
|
-
"test-engineer": {
|
|
146
|
-
profile: "gpt56_terra_high",
|
|
147
|
-
timeoutSec: 1200,
|
|
148
|
-
runMode: "bg",
|
|
149
|
-
opusOversight: false,
|
|
150
|
-
mcpHint: "implement",
|
|
151
|
-
},
|
|
152
|
-
"qa-tester": {
|
|
153
|
-
profile: "gpt56_terra_high",
|
|
154
|
-
timeoutSec: 1200,
|
|
155
|
-
runMode: "bg",
|
|
156
|
-
opusOversight: false,
|
|
157
|
-
mcpHint: "review",
|
|
158
|
-
subcommand: "review",
|
|
159
|
-
},
|
|
160
|
-
spark: {
|
|
161
|
-
profile: "gpt56_luna_low",
|
|
162
|
-
timeoutSec: 180,
|
|
163
|
-
runMode: "fg",
|
|
164
|
-
opusOversight: false,
|
|
165
|
-
mcpHint: "implement",
|
|
166
|
-
},
|
|
167
|
-
};
|
|
14
|
+
import {
|
|
15
|
+
CODEX_AGENT_POLICY,
|
|
16
|
+
resolveCodexAgentPolicy,
|
|
17
|
+
resolveCodexAgentProfile,
|
|
18
|
+
resolveNestedCodexAgentProfile,
|
|
19
|
+
} from "./agent-route-policy.mjs";
|
|
168
20
|
|
|
169
|
-
|
|
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 };
|
|
170
24
|
|
|
171
25
|
export function plan({
|
|
172
26
|
agent,
|
|
@@ -174,18 +28,26 @@ export function plan({
|
|
|
174
28
|
mcpProfile = "auto",
|
|
175
29
|
timeoutSec,
|
|
176
30
|
contextFile,
|
|
31
|
+
profileOverride = "auto",
|
|
32
|
+
retryProfile = null,
|
|
33
|
+
nested = false,
|
|
177
34
|
} = {}) {
|
|
178
35
|
if (!agent) {
|
|
179
36
|
throw new Error("[cli-codex] agent required");
|
|
180
37
|
}
|
|
181
|
-
const cfg =
|
|
38
|
+
const cfg = resolveCodexAgentPolicy(agent);
|
|
39
|
+
const profile = resolveCodexAgentProfile(agent, {
|
|
40
|
+
profileOverride,
|
|
41
|
+
retryProfile,
|
|
42
|
+
nested,
|
|
43
|
+
});
|
|
182
44
|
const effectiveTimeoutSec =
|
|
183
45
|
Number.isFinite(timeoutSec) && timeoutSec > 0 ? timeoutSec : cfg.timeoutSec;
|
|
184
46
|
return {
|
|
185
47
|
command,
|
|
186
48
|
subcommand: cfg.subcommand ?? "exec",
|
|
187
|
-
profile
|
|
188
|
-
effort:
|
|
49
|
+
profile,
|
|
50
|
+
effort: profile,
|
|
189
51
|
timeoutMs: effectiveTimeoutSec * 1000,
|
|
190
52
|
runMode: cfg.runMode,
|
|
191
53
|
opusOversight: cfg.opusOversight,
|
|
@@ -196,5 +58,5 @@ export function plan({
|
|
|
196
58
|
}
|
|
197
59
|
|
|
198
60
|
export function describe() {
|
|
199
|
-
return { id, cliType, command, agents: Object.keys(
|
|
61
|
+
return { id, cliType, command, agents: Object.keys(CODEX_AGENT_POLICY) };
|
|
200
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(
|
|
@@ -49,8 +60,10 @@ function readProfileScalar(raw, key) {
|
|
|
49
60
|
* @param {{ codexHome?: string }} [opts]
|
|
50
61
|
* @returns {string[]} e.g. ['model="gpt-5.6-terra"', 'model_reasoning_effort="high"']
|
|
51
62
|
*/
|
|
52
|
-
export function
|
|
53
|
-
if (typeof profileName !== "string" || !profileName)
|
|
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
|
|
package/scripts/lint-skills.mjs
CHANGED
|
@@ -121,6 +121,66 @@ function lintBodyModelNames({ file, body, startLine }) {
|
|
|
121
121
|
return problems;
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
function lintAgentModels({ file, body, startLine }) {
|
|
125
|
+
const problems = [];
|
|
126
|
+
const agentStartRe = /\bAgent\s*\(/g;
|
|
127
|
+
let match;
|
|
128
|
+
while ((match = agentStartRe.exec(body)) !== null) {
|
|
129
|
+
const openParenIndex = match.index + match[0].lastIndexOf("(");
|
|
130
|
+
let quote = null;
|
|
131
|
+
let depth = 1;
|
|
132
|
+
let closeParenIndex = -1;
|
|
133
|
+
|
|
134
|
+
for (let index = openParenIndex + 1; index < body.length; index += 1) {
|
|
135
|
+
const character = body[index];
|
|
136
|
+
|
|
137
|
+
if (quote) {
|
|
138
|
+
if (character === "\\") {
|
|
139
|
+
index += 1;
|
|
140
|
+
} else if (character === quote) {
|
|
141
|
+
quote = null;
|
|
142
|
+
}
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (character === '"' || character === "'" || character === "`") {
|
|
147
|
+
quote = character;
|
|
148
|
+
} else if (character === "(") {
|
|
149
|
+
depth += 1;
|
|
150
|
+
} else if (character === ")") {
|
|
151
|
+
depth -= 1;
|
|
152
|
+
if (depth === 0) {
|
|
153
|
+
closeParenIndex = index;
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (closeParenIndex === -1) continue;
|
|
160
|
+
|
|
161
|
+
const call = body.slice(openParenIndex + 1, closeParenIndex);
|
|
162
|
+
agentStartRe.lastIndex = closeParenIndex + 1;
|
|
163
|
+
if (!/\bsubagent_type\s*=/.test(call) || !/\bprompt\s*=/.test(call)) {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (/\bmodel\s*=/.test(call)) continue;
|
|
167
|
+
const line =
|
|
168
|
+
startLine + body.slice(0, match.index).split(/\r?\n/).length - 1;
|
|
169
|
+
problems.push(
|
|
170
|
+
createProblem({
|
|
171
|
+
file,
|
|
172
|
+
line,
|
|
173
|
+
code: "agent-model-required",
|
|
174
|
+
problem: "executable Agent call is missing model=",
|
|
175
|
+
cause:
|
|
176
|
+
"실행형 Agent 호출은 모델 선택을 명시해야 라우팅 drift를 막을 수 있습니다.",
|
|
177
|
+
fix: "Add model=<resolved-native-model> to the Agent call.",
|
|
178
|
+
}),
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
return problems;
|
|
182
|
+
}
|
|
183
|
+
|
|
124
184
|
function lintSkillFile({ file, rootDir }) {
|
|
125
185
|
const content = readFileSync(file, "utf8");
|
|
126
186
|
const { data, body } = parseFrontmatter(content);
|
|
@@ -133,6 +193,11 @@ function lintSkillFile({ file, rootDir }) {
|
|
|
133
193
|
body,
|
|
134
194
|
startLine: bodyStartLine(content),
|
|
135
195
|
}),
|
|
196
|
+
...lintAgentModels({
|
|
197
|
+
file: relativeFile,
|
|
198
|
+
body,
|
|
199
|
+
startLine: bodyStartLine(content),
|
|
200
|
+
}),
|
|
136
201
|
];
|
|
137
202
|
}
|
|
138
203
|
|
package/scripts/pack.mjs
CHANGED
|
@@ -34,6 +34,8 @@ const CORE_FILES = [
|
|
|
34
34
|
"hub/intent.mjs",
|
|
35
35
|
"hub/token-mode.mjs",
|
|
36
36
|
"hub/router.mjs",
|
|
37
|
+
"hub/role-contract.mjs",
|
|
38
|
+
"hub/role-control-events.mjs",
|
|
37
39
|
"hub/hitl.mjs",
|
|
38
40
|
"hub/assign-callbacks.mjs",
|
|
39
41
|
"hub/fullcycle.mjs",
|
|
@@ -51,6 +53,8 @@ const CORE_FILES = [
|
|
|
51
53
|
// Phase 1 dynamic routing (#251 + #263 + #264 + wire-ups)
|
|
52
54
|
"hub/dynamic-routing-engine.mjs",
|
|
53
55
|
"hub/routing-snapshot.mjs",
|
|
56
|
+
"hub/role-contract.mjs",
|
|
57
|
+
"hub/role-control-events.mjs",
|
|
54
58
|
];
|
|
55
59
|
|
|
56
60
|
const CORE_DIRS = [
|
|
@@ -60,6 +64,7 @@ const CORE_DIRS = [
|
|
|
60
64
|
"hub/delegator",
|
|
61
65
|
"hub/lib",
|
|
62
66
|
"hub/middleware",
|
|
67
|
+
"hub/team/intervention.mjs",
|
|
63
68
|
"hub/team/retry-state-machine.mjs",
|
|
64
69
|
"hub/team/claude-agent-session-normalizer.mjs",
|
|
65
70
|
"hub/team/claude-daemon-control.mjs",
|
|
@@ -88,6 +93,7 @@ const REMOTE_FILES = [
|
|
|
88
93
|
"cto/brief.mjs",
|
|
89
94
|
"cto/collect.mjs",
|
|
90
95
|
"cto/events.mjs",
|
|
96
|
+
"cto/hygiene-actions.mjs",
|
|
91
97
|
"cto/hygiene-notify.mjs",
|
|
92
98
|
"cto/hygiene.mjs",
|
|
93
99
|
"cto/lake-root.mjs",
|
|
@@ -96,6 +102,7 @@ const REMOTE_FILES = [
|
|
|
96
102
|
];
|
|
97
103
|
|
|
98
104
|
const REMOTE_DIRS = ["hub/team", "hub/workers", "hub/public"];
|
|
105
|
+
const REMOTE_CORE_PROXIES = ["hub/lib/cto-env.mjs"];
|
|
99
106
|
|
|
100
107
|
const TRIFLUX_DIRS = [
|
|
101
108
|
"bin",
|
|
@@ -301,6 +308,12 @@ function packRemote() {
|
|
|
301
308
|
cleanDist(dest);
|
|
302
309
|
for (const f of REMOTE_FILES) copyItem(f, dest);
|
|
303
310
|
for (const d of REMOTE_DIRS) copyItem(d, dest);
|
|
311
|
+
for (const proxy of REMOTE_CORE_PROXIES) {
|
|
312
|
+
const target = join(dest, proxy);
|
|
313
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
314
|
+
writeFileSync(target, `export * from "@triflux/core/${proxy}";\n`);
|
|
315
|
+
console.log(` WRITE ${proxy}`);
|
|
316
|
+
}
|
|
304
317
|
// shared scripts/lib needed by delegator-mcp.mjs (dynamic resolve)
|
|
305
318
|
copyItem("scripts/lib", dest);
|
|
306
319
|
writeIndex(dest, REMOTE_INDEX);
|
|
@@ -22,6 +22,7 @@ import { fileURLToPath } from "node:url";
|
|
|
22
22
|
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
|
|
23
23
|
const DEFAULT_REPO_ROOT = join(SCRIPT_DIR, "..", "..");
|
|
24
24
|
const MIRROR_TOPS = [
|
|
25
|
+
"adapters",
|
|
25
26
|
"bin",
|
|
26
27
|
"config",
|
|
27
28
|
"cto",
|
|
@@ -38,6 +39,10 @@ const CORE_FILE_MIRRORS = [
|
|
|
38
39
|
source: "hub/bridge.mjs",
|
|
39
40
|
target: "packages/core/hub/bridge.mjs",
|
|
40
41
|
},
|
|
42
|
+
{
|
|
43
|
+
source: "hub/router.mjs",
|
|
44
|
+
target: "packages/core/hub/router.mjs",
|
|
45
|
+
},
|
|
41
46
|
{
|
|
42
47
|
source: "hub/team/retry-state-machine.mjs",
|
|
43
48
|
target: "packages/core/hub/team/retry-state-machine.mjs",
|
package/scripts/setup.mjs
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
mkdirSync,
|
|
15
15
|
readdirSync,
|
|
16
16
|
readFileSync,
|
|
17
|
+
renameSync,
|
|
17
18
|
rmSync,
|
|
18
19
|
unlinkSync,
|
|
19
20
|
writeFileSync,
|
|
@@ -73,7 +74,15 @@ const HUD_PATH = join(CLAUDE_DIR, "hud", "hud-qos-status.mjs");
|
|
|
73
74
|
const WINDOWS_HUB_AUTOSTART_TASK = "TrifluxHubEnsure";
|
|
74
75
|
|
|
75
76
|
const REQUIRED_CODEX_PROFILES = [
|
|
76
|
-
// GPT-5.6 family —
|
|
77
|
+
// GPT-5.6 family — max/ultra are explicit exception lanes, not role defaults.
|
|
78
|
+
{
|
|
79
|
+
name: "gpt56_sol_ultra",
|
|
80
|
+
lines: ['model = "gpt-5.6-sol"', 'model_reasoning_effort = "ultra"'],
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: "gpt56_sol_max",
|
|
84
|
+
lines: ['model = "gpt-5.6-sol"', 'model_reasoning_effort = "max"'],
|
|
85
|
+
},
|
|
77
86
|
{
|
|
78
87
|
name: "gpt56_sol_xhigh",
|
|
79
88
|
lines: ['model = "gpt-5.6-sol"', 'model_reasoning_effort = "xhigh"'],
|
|
@@ -498,6 +507,7 @@ const SKILL_ALIASES = [
|
|
|
498
507
|
|
|
499
508
|
const DEPRECATED_SKILLS = ["tfx-codex-route", "tfx-gemini-route"];
|
|
500
509
|
const LOCAL_DEV_SKILL_MARKER = ".triflux-local-skill";
|
|
510
|
+
const MANAGED_CODEX_SKILL_MARKER = ".triflux-managed-skill";
|
|
501
511
|
|
|
502
512
|
// ── 구형 Codex 모델 (마이그레이션 안내 대상) ──
|
|
503
513
|
|
|
@@ -579,6 +589,76 @@ function isLocalDevSkillDir(skillPath) {
|
|
|
579
589
|
return existsSync(join(skillPath, LOCAL_DEV_SKILL_MARKER));
|
|
580
590
|
}
|
|
581
591
|
|
|
592
|
+
function skillTreeMatches(srcDir, dstDir) {
|
|
593
|
+
if (!existsSync(srcDir) || !existsSync(dstDir)) return false;
|
|
594
|
+
for (const entry of readdirSync(srcDir, { withFileTypes: true })) {
|
|
595
|
+
const srcPath = join(srcDir, entry.name);
|
|
596
|
+
const dstPath = join(dstDir, entry.name);
|
|
597
|
+
if (entry.isDirectory()) {
|
|
598
|
+
if (!skillTreeMatches(srcPath, dstPath)) return false;
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
601
|
+
if (!existsSync(dstPath)) return false;
|
|
602
|
+
if (!readFileSync(srcPath).equals(readFileSync(dstPath))) return false;
|
|
603
|
+
}
|
|
604
|
+
return true;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* Sync the tracked Codex tfx-harness adapter without taking ownership of a
|
|
609
|
+
* pre-existing user skill. A missing source is a hard failure; a conflicting
|
|
610
|
+
* destination is backed up and deliberately left untouched.
|
|
611
|
+
*/
|
|
612
|
+
function syncCodexHarnessAdapter({
|
|
613
|
+
sourceDir = join(PLUGIN_ROOT, "adapters", "codex", "skills", "tfx-harness"),
|
|
614
|
+
destinationDir = join(CODEX_DIR, "skills", "tfx-harness"),
|
|
615
|
+
} = {}) {
|
|
616
|
+
const sourceSkill = join(sourceDir, "SKILL.md");
|
|
617
|
+
if (!existsSync(sourceSkill)) {
|
|
618
|
+
return {
|
|
619
|
+
ok: false,
|
|
620
|
+
action: "blocked",
|
|
621
|
+
reason: "codex_harness_adapter_source_missing",
|
|
622
|
+
sourceDir,
|
|
623
|
+
destinationDir,
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const managed = existsSync(join(destinationDir, MANAGED_CODEX_SKILL_MARKER));
|
|
628
|
+
const current = skillTreeMatches(sourceDir, destinationDir);
|
|
629
|
+
if (current && managed) {
|
|
630
|
+
return { ok: true, action: "noop", sourceDir, destinationDir };
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
if (existsSync(destinationDir) && !managed) {
|
|
634
|
+
const backupDir = `${destinationDir}.triflux-backup-${Date.now()}`;
|
|
635
|
+
cpSync(destinationDir, backupDir, { recursive: true });
|
|
636
|
+
return {
|
|
637
|
+
ok: true,
|
|
638
|
+
action: "skipped",
|
|
639
|
+
reason: "user_owned_codex_skill",
|
|
640
|
+
sourceDir,
|
|
641
|
+
destinationDir,
|
|
642
|
+
backupDir,
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
const tempDir = `${destinationDir}.triflux-tmp-${process.pid}-${Date.now()}`;
|
|
647
|
+
mkdirSync(dirname(destinationDir), { recursive: true });
|
|
648
|
+
cpSync(sourceDir, tempDir, { recursive: true });
|
|
649
|
+
writeFileSync(
|
|
650
|
+
join(tempDir, MANAGED_CODEX_SKILL_MARKER),
|
|
651
|
+
"managed by triflux\n",
|
|
652
|
+
);
|
|
653
|
+
|
|
654
|
+
if (existsSync(destinationDir)) {
|
|
655
|
+
const previousDir = `${destinationDir}.triflux-previous-${Date.now()}`;
|
|
656
|
+
renameSync(destinationDir, previousDir);
|
|
657
|
+
}
|
|
658
|
+
renameSync(tempDir, destinationDir);
|
|
659
|
+
return { ok: true, action: "synced", sourceDir, destinationDir };
|
|
660
|
+
}
|
|
661
|
+
|
|
582
662
|
/**
|
|
583
663
|
* 훅 커맨드 문자열에서 스크립트 파일명을 추출한다.
|
|
584
664
|
* 예: 'node "/path/to/safety-guard.mjs"' → "safety-guard.mjs"
|
|
@@ -1526,6 +1606,7 @@ export {
|
|
|
1526
1606
|
SYNC_MAP,
|
|
1527
1607
|
scanHudFiles,
|
|
1528
1608
|
syncAliasedSkillDir,
|
|
1609
|
+
syncCodexHarnessAdapter,
|
|
1529
1610
|
syncWorkerPackages,
|
|
1530
1611
|
validateSchtasksTrLength,
|
|
1531
1612
|
WINDOWS_HUB_AUTOSTART_TASK,
|
|
@@ -1581,6 +1662,16 @@ export async function runDeferred(stdinData) {
|
|
|
1581
1662
|
result.action === "updated" ||
|
|
1582
1663
|
result.action === "removed",
|
|
1583
1664
|
).length;
|
|
1665
|
+
const codexHarnessSync = syncCodexHarnessAdapter();
|
|
1666
|
+
if (!codexHarnessSync.ok) {
|
|
1667
|
+
io.log(` \x1b[31m✗\x1b[0m Codex tfx-harness: ${codexHarnessSync.reason}`);
|
|
1668
|
+
return io.result(1);
|
|
1669
|
+
}
|
|
1670
|
+
if (codexHarnessSync.action === "skipped") {
|
|
1671
|
+
io.log(
|
|
1672
|
+
` \x1b[33m⚠\x1b[0m Codex tfx-harness: user skill preserved; backup ${codexHarnessSync.backupDir}`,
|
|
1673
|
+
);
|
|
1674
|
+
}
|
|
1584
1675
|
const cloakBrowserResult = ensureCloakBrowser({
|
|
1585
1676
|
warn: (message) => io.log(` \x1b[33m⚠\x1b[0m ${message}`),
|
|
1586
1677
|
});
|
|
@@ -1601,7 +1692,8 @@ export async function runDeferred(stdinData) {
|
|
|
1601
1692
|
return io.result(0);
|
|
1602
1693
|
}
|
|
1603
1694
|
|
|
1604
|
-
let synced =
|
|
1695
|
+
let synced =
|
|
1696
|
+
claudeRoutingChangedCount + (codexHarnessSync.action === "synced" ? 1 : 0);
|
|
1605
1697
|
|
|
1606
1698
|
// ── Memory Doctor (P0 자동 수정) ──
|
|
1607
1699
|
const isCIEnv = process.env.CI === "true" || process.env.DOCKER === "true";
|
package/scripts/tfx-route.mjs
CHANGED
|
@@ -165,6 +165,8 @@ export function buildRoutePlan(request, agentMap) {
|
|
|
165
165
|
mcpProfile: request.mcpProfile ?? "auto",
|
|
166
166
|
timeoutSec: request.timeoutSec,
|
|
167
167
|
contextFile: request.contextFile,
|
|
168
|
+
profileOverride: request.profileOverride,
|
|
169
|
+
nested: request.nested,
|
|
168
170
|
});
|
|
169
171
|
return {
|
|
170
172
|
agent: request.agent,
|
|
@@ -272,7 +274,18 @@ async function main(argv) {
|
|
|
272
274
|
}
|
|
273
275
|
|
|
274
276
|
const agentMap = loadAgentMap();
|
|
275
|
-
const plan = buildRoutePlan(
|
|
277
|
+
const plan = buildRoutePlan(
|
|
278
|
+
{
|
|
279
|
+
...request,
|
|
280
|
+
profileOverride: process.env.TFX_CODEX_PROFILE,
|
|
281
|
+
nested: Boolean(
|
|
282
|
+
process.env.TFX_TEAM_NAME ||
|
|
283
|
+
process.env.TFX_WORKER_INDEX ||
|
|
284
|
+
process.env.TFX_WORKER_SANDBOX_SCOPE,
|
|
285
|
+
),
|
|
286
|
+
},
|
|
287
|
+
agentMap,
|
|
288
|
+
);
|
|
276
289
|
|
|
277
290
|
if (request.mode === "route-print") {
|
|
278
291
|
process.stdout.write(`${JSON.stringify(plan, null, 2)}\n`);
|