@triflux/remote 10.86.1 → 10.88.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/hub/team/cli/commands/debug.mjs +11 -4
- package/hub/team/handoff.mjs +100 -0
- package/hub/team/headless.mjs +49 -10
- package/hub/team/lead-launch.mjs +186 -0
- package/hub/team/native-peer.mjs +97 -3
- package/hub/team/orchestrator.mjs +80 -0
- package/hub/team/pane.mjs +99 -0
- package/hub/team/results-index.mjs +35 -0
- package/hub/team/retry-state-machine.mjs +97 -9
- package/hub/team/uds-orchestrator.mjs +30 -8
- package/package.json +1 -1
- package/scripts/lib/agent-route-policy.mjs +39 -28
- package/scripts/lib/codex-profile-config.mjs +40 -59
- package/scripts/lib/doctor-env-checks.mjs +377 -0
- package/scripts/lib/gemini-profiles.mjs +2 -5
- package/scripts/lib/mcp-filter.mjs +86 -3
- package/scripts/lib/model-effort.mjs +134 -0
- package/scripts/lib/model-family-resolver.mjs +73 -8
|
@@ -9,6 +9,11 @@ import {
|
|
|
9
9
|
import { homedir } from "node:os";
|
|
10
10
|
import { dirname, join } from "node:path";
|
|
11
11
|
import { writeRotatedBackup } from "./backup-rotation.mjs";
|
|
12
|
+
import {
|
|
13
|
+
modelEffortFromProfile,
|
|
14
|
+
normalizeCodexProfileName,
|
|
15
|
+
resolveCodexModelEffort,
|
|
16
|
+
} from "./model-effort.mjs";
|
|
12
17
|
import {
|
|
13
18
|
CODEX_FAMILY_PROFILES,
|
|
14
19
|
modelFamilyPaths,
|
|
@@ -16,46 +21,42 @@ import {
|
|
|
16
21
|
writeModelFile,
|
|
17
22
|
} from "./model-family-resolver.mjs";
|
|
18
23
|
|
|
19
|
-
|
|
20
|
-
ultra: "ultra",
|
|
21
|
-
max: "max",
|
|
22
|
-
xhigh: "xhigh",
|
|
23
|
-
high: "high",
|
|
24
|
-
med: "medium",
|
|
25
|
-
medium: "medium",
|
|
26
|
-
low: "low",
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const LEGACY_CANONICAL_PROFILE_ALIASES = Object.freeze({
|
|
30
|
-
gpt56_sol_xhigh: "astra_xhigh",
|
|
31
|
-
gpt56_sol_max: "astra_max",
|
|
32
|
-
gpt56_sol_ultra: "astra_ultra",
|
|
33
|
-
gpt56_terra_high: "sol_high",
|
|
34
|
-
gpt56_terra_med: "sol_med",
|
|
35
|
-
});
|
|
24
|
+
export { normalizeCodexProfileName };
|
|
36
25
|
|
|
37
|
-
export function
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
26
|
+
export function managedCodexProfileContents(opts = {}) {
|
|
27
|
+
const { profiles, families } = resolveModelFamilies("codex", opts);
|
|
28
|
+
const contents = {};
|
|
29
|
+
for (const [name, { model, effort }] of Object.entries(profiles)) {
|
|
30
|
+
contents[name] =
|
|
31
|
+
`model = "${model}"\nmodel_reasoning_effort = "${effort}"\n`;
|
|
32
|
+
}
|
|
33
|
+
for (const [family, { model }] of Object.entries(families)) {
|
|
34
|
+
contents[family] = `model = "${model}"\n`;
|
|
35
|
+
}
|
|
36
|
+
return { profiles, contents };
|
|
45
37
|
}
|
|
46
38
|
|
|
47
39
|
export function ensureManagedCodexProfiles(opts = {}) {
|
|
48
40
|
const { codexHome } = modelFamilyPaths(opts);
|
|
49
|
-
const { profiles } =
|
|
41
|
+
const { profiles, contents } = managedCodexProfileContents(opts);
|
|
50
42
|
let changed = 0;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
43
|
+
const families = new Set(
|
|
44
|
+
Object.values(CODEX_FAMILY_PROFILES).map(({ family }) => family),
|
|
45
|
+
);
|
|
46
|
+
for (const [name, content] of Object.entries(contents)) {
|
|
47
|
+
const path = join(codexHome, `${name}.config.toml`);
|
|
48
|
+
if (families.has(name) && existsSync(path)) {
|
|
49
|
+
const existing = readFileSync(path, "utf8");
|
|
50
|
+
if (existing !== content) {
|
|
51
|
+
const backup = writeRotatedBackup(path, existing, {
|
|
52
|
+
label: "bak-model-family",
|
|
53
|
+
suffix: stamp(),
|
|
54
|
+
mode: privateFileMode(path),
|
|
55
|
+
});
|
|
56
|
+
opts.notice?.(`Codex 가족 프로필 백업: ${backup}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (writeModelFile(path, content)) changed++;
|
|
59
60
|
}
|
|
60
61
|
return { changed, profiles };
|
|
61
62
|
}
|
|
@@ -72,45 +73,25 @@ function readProfileScalar(raw, key) {
|
|
|
72
73
|
return value || null;
|
|
73
74
|
}
|
|
74
75
|
|
|
75
|
-
|
|
76
|
-
* Resolve a Codex effort profile name (e.g. "sol_high") to the `-c key=value`
|
|
77
|
-
* config overrides that `codex exec` accepts, so the headless CLI lanes can
|
|
78
|
-
* select a profile WITHOUT `codex exec --profile <name>`. codex 0.134+ rejects
|
|
79
|
-
* `--profile X` whenever config.toml still contains an inline `[profiles.X]`
|
|
80
|
-
* table (which codex itself re-injects when it rewrites config.toml), so the
|
|
81
|
-
* `-c` form is immune and needs no config mutation / sanitize race.
|
|
82
|
-
*
|
|
83
|
-
* SSOT = the per-profile file `~/.codex/<name>.config.toml` written by
|
|
84
|
-
* `tfx setup`; falls back to deriving the effort from the `<...>_<effort>`
|
|
85
|
-
* naming convention when the file is absent. Returns the keys that resolve, in
|
|
86
|
-
* `key="value"` TOML-scalar form ready to be shell-quoted by the caller.
|
|
87
|
-
*
|
|
88
|
-
* @param {string} profileName
|
|
89
|
-
* @param {{ codexHome?: string }} [opts]
|
|
90
|
-
* @returns {string[]} e.g. ['model="gpt-6.1-sol"', 'model_reasoning_effort="high"']
|
|
91
|
-
*/
|
|
76
|
+
// 관리 이름의 effort는 변환 표가 정한다. custom 파일은 ultra 제한 판정에 사용한다.
|
|
92
77
|
export function resolveCodexProfileConfigValues(profileName, opts = {}) {
|
|
93
78
|
if (typeof profileName !== "string" || !profileName) {
|
|
94
79
|
return { model: null, effort: null };
|
|
95
80
|
}
|
|
96
81
|
const normalizedProfileName = normalizeCodexProfileName(profileName);
|
|
82
|
+
const managed = modelEffortFromProfile("codex", normalizedProfileName);
|
|
97
83
|
const codexHome =
|
|
98
84
|
opts.codexHome || process.env.CODEX_HOME || join(homedir(), ".codex");
|
|
99
85
|
let model = null;
|
|
100
|
-
let effort = null;
|
|
86
|
+
let effort = managed ? resolveCodexModelEffort(managed, opts).effort : null;
|
|
101
87
|
try {
|
|
102
88
|
const raw = readFileSync(
|
|
103
89
|
join(codexHome, `${normalizedProfileName}.config.toml`),
|
|
104
90
|
"utf8",
|
|
105
91
|
);
|
|
106
92
|
model = readProfileScalar(raw, "model");
|
|
107
|
-
effort = readProfileScalar(raw, "model_reasoning_effort");
|
|
108
|
-
} catch {
|
|
109
|
-
const suffix = /_(ultra|max|xhigh|high|med|medium|low)$/.exec(
|
|
110
|
-
normalizedProfileName,
|
|
111
|
-
);
|
|
112
|
-
if (suffix) effort = EFFORT_BY_SUFFIX[suffix[1]] || null;
|
|
113
|
-
}
|
|
93
|
+
if (!managed) effort = readProfileScalar(raw, "model_reasoning_effort");
|
|
94
|
+
} catch {}
|
|
114
95
|
return { model, effort };
|
|
115
96
|
}
|
|
116
97
|
|
|
@@ -1,4 +1,381 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import {
|
|
4
|
+
chmodSync,
|
|
5
|
+
copyFileSync,
|
|
6
|
+
existsSync,
|
|
7
|
+
readFileSync,
|
|
8
|
+
realpathSync,
|
|
9
|
+
renameSync,
|
|
10
|
+
unlinkSync,
|
|
11
|
+
writeFileSync,
|
|
12
|
+
} from "node:fs";
|
|
13
|
+
|
|
14
|
+
const defaultFs = {
|
|
15
|
+
chmodSync,
|
|
16
|
+
copyFileSync,
|
|
17
|
+
existsSync,
|
|
18
|
+
readFileSync,
|
|
19
|
+
realpathSync,
|
|
20
|
+
renameSync,
|
|
21
|
+
unlinkSync,
|
|
22
|
+
writeFileSync,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// ARM64 Mach-O agy 바이너리의 time.After 함수와 유휴 대기 호출부 시그니처 (#1022 peer-wake 패치)
|
|
26
|
+
export const AGY_TIME_AFTER_SIG = Buffer.from(
|
|
27
|
+
"900b40f9ff6330eb29010054fe0f1ef8fd831ff8fd2300d16affff97000040f9fd835ff8fe0742f8c0035fd6",
|
|
28
|
+
"hex",
|
|
29
|
+
);
|
|
30
|
+
export const AGY_PEER_WAKE_ORIG_CALL = Buffer.from(
|
|
31
|
+
"e13740f9220041a9423040f9210c40a9e40302aae20303aa80003fd6",
|
|
32
|
+
"hex",
|
|
33
|
+
);
|
|
34
|
+
export const AGY_PEER_WAKE_PATCH_PREFIX = Buffer.from(
|
|
35
|
+
"1f2003d51f2003d51f2003d51f2003d5004099524073a772",
|
|
36
|
+
"hex",
|
|
37
|
+
);
|
|
38
|
+
export const AGY_PEER_WAKE_ORIG_STP = Buffer.from("e08708a9", "hex");
|
|
39
|
+
export const AGY_PEER_WAKE_PATCH_STP = Buffer.from("e0ff08a9", "hex");
|
|
40
|
+
const AGY_PEER_WAKE_STP_DELTA = 0x38;
|
|
41
|
+
|
|
42
|
+
export function encodeArm64Bl(pcOffset, targetOffset) {
|
|
43
|
+
const delta = targetOffset - pcOffset;
|
|
44
|
+
if (delta % 4 !== 0 || delta < -0x8000000 || delta > 0x7fffffc) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
const imm26 = (delta >> 2) & 0x03ffffff;
|
|
48
|
+
const out = Buffer.alloc(4);
|
|
49
|
+
out.writeUInt32LE((0x94000000 | imm26) >>> 0, 0);
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function findUniqueOffset(buf, pattern) {
|
|
54
|
+
const first = buf.indexOf(pattern);
|
|
55
|
+
if (first === -1) return -1;
|
|
56
|
+
if (buf.indexOf(pattern, first + 1) !== -1) return -2;
|
|
57
|
+
return first;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function inspectAgyPeerWakePatch({
|
|
61
|
+
agyPath,
|
|
62
|
+
platform = process.platform,
|
|
63
|
+
arch = process.arch,
|
|
64
|
+
fs = defaultFs,
|
|
65
|
+
} = {}) {
|
|
66
|
+
if (platform !== "darwin" || arch !== "arm64") {
|
|
67
|
+
return { ok: true, status: "skipped", platform, arch };
|
|
68
|
+
}
|
|
69
|
+
if (!agyPath) {
|
|
70
|
+
return { ok: true, status: "skipped", reason: "agy-not-installed" };
|
|
71
|
+
}
|
|
72
|
+
let resolvedPath = agyPath;
|
|
73
|
+
try {
|
|
74
|
+
resolvedPath = fs.realpathSync(agyPath);
|
|
75
|
+
} catch {
|
|
76
|
+
return { ok: true, status: "skipped", reason: "agy-not-found" };
|
|
77
|
+
}
|
|
78
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
79
|
+
return { ok: true, status: "skipped", reason: "agy-not-found" };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const patchJsonPath = `${resolvedPath}.patch.json`;
|
|
83
|
+
let buf;
|
|
84
|
+
try {
|
|
85
|
+
buf = fs.readFileSync(resolvedPath);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
return {
|
|
88
|
+
ok: false,
|
|
89
|
+
status: "error",
|
|
90
|
+
patchable: false,
|
|
91
|
+
path: resolvedPath,
|
|
92
|
+
patchJsonPath,
|
|
93
|
+
reason: "read-failed",
|
|
94
|
+
message: error?.message || String(error),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
const sha256 = createHash("sha256").update(buf).digest("hex");
|
|
98
|
+
const timeAfterOffset = findUniqueOffset(buf, AGY_TIME_AFTER_SIG);
|
|
99
|
+
if (timeAfterOffset < 0) {
|
|
100
|
+
return {
|
|
101
|
+
ok: false,
|
|
102
|
+
status: "unsupported",
|
|
103
|
+
patchable: false,
|
|
104
|
+
path: resolvedPath,
|
|
105
|
+
patchJsonPath,
|
|
106
|
+
sha256,
|
|
107
|
+
reason:
|
|
108
|
+
timeAfterOffset === -2
|
|
109
|
+
? "ambiguous-time-after-signature"
|
|
110
|
+
: "time-after-signature-not-found",
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const patchedOffset = findUniqueOffset(buf, AGY_PEER_WAKE_PATCH_PREFIX);
|
|
115
|
+
if (
|
|
116
|
+
patchedOffset >= 0 &&
|
|
117
|
+
patchedOffset + AGY_PEER_WAKE_STP_DELTA + 4 <= buf.length
|
|
118
|
+
) {
|
|
119
|
+
const expectedBl = encodeArm64Bl(
|
|
120
|
+
patchedOffset + AGY_PEER_WAKE_PATCH_PREFIX.length,
|
|
121
|
+
timeAfterOffset,
|
|
122
|
+
);
|
|
123
|
+
const actualBl = buf.subarray(
|
|
124
|
+
patchedOffset + AGY_PEER_WAKE_PATCH_PREFIX.length,
|
|
125
|
+
patchedOffset + AGY_PEER_WAKE_PATCH_PREFIX.length + 4,
|
|
126
|
+
);
|
|
127
|
+
const actualStp = buf.subarray(
|
|
128
|
+
patchedOffset + AGY_PEER_WAKE_STP_DELTA,
|
|
129
|
+
patchedOffset + AGY_PEER_WAKE_STP_DELTA + 4,
|
|
130
|
+
);
|
|
131
|
+
if (
|
|
132
|
+
expectedBl &&
|
|
133
|
+
actualBl.equals(expectedBl) &&
|
|
134
|
+
actualStp.equals(AGY_PEER_WAKE_PATCH_STP)
|
|
135
|
+
) {
|
|
136
|
+
let metadataOk = false;
|
|
137
|
+
if (fs.existsSync(patchJsonPath)) {
|
|
138
|
+
try {
|
|
139
|
+
const meta = JSON.parse(fs.readFileSync(patchJsonPath, "utf8"));
|
|
140
|
+
metadataOk =
|
|
141
|
+
meta?.sha256 === sha256 &&
|
|
142
|
+
Array.isArray(meta?.patches) &&
|
|
143
|
+
meta.patches.includes("peer-wake");
|
|
144
|
+
} catch {
|
|
145
|
+
metadataOk = false;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (metadataOk) {
|
|
149
|
+
return {
|
|
150
|
+
ok: true,
|
|
151
|
+
status: "patched",
|
|
152
|
+
patchable: false,
|
|
153
|
+
binaryPatched: true,
|
|
154
|
+
path: resolvedPath,
|
|
155
|
+
patchJsonPath,
|
|
156
|
+
sha256,
|
|
157
|
+
callOffset: patchedOffset,
|
|
158
|
+
timeAfterOffset,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
ok: false,
|
|
163
|
+
status: "metadata-outdated",
|
|
164
|
+
patchable: true,
|
|
165
|
+
binaryPatched: true,
|
|
166
|
+
path: resolvedPath,
|
|
167
|
+
patchJsonPath,
|
|
168
|
+
sha256,
|
|
169
|
+
callOffset: patchedOffset,
|
|
170
|
+
timeAfterOffset,
|
|
171
|
+
fix: "tfx doctor --fix",
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const origOffset = findUniqueOffset(buf, AGY_PEER_WAKE_ORIG_CALL);
|
|
177
|
+
if (
|
|
178
|
+
origOffset >= 0 &&
|
|
179
|
+
origOffset + AGY_PEER_WAKE_STP_DELTA + 4 <= buf.length
|
|
180
|
+
) {
|
|
181
|
+
const expectedBl = encodeArm64Bl(
|
|
182
|
+
origOffset + AGY_PEER_WAKE_PATCH_PREFIX.length,
|
|
183
|
+
timeAfterOffset,
|
|
184
|
+
);
|
|
185
|
+
const actualStp = buf.subarray(
|
|
186
|
+
origOffset + AGY_PEER_WAKE_STP_DELTA,
|
|
187
|
+
origOffset + AGY_PEER_WAKE_STP_DELTA + 4,
|
|
188
|
+
);
|
|
189
|
+
if (expectedBl && actualStp.equals(AGY_PEER_WAKE_ORIG_STP)) {
|
|
190
|
+
return {
|
|
191
|
+
ok: false,
|
|
192
|
+
status: "unpatched",
|
|
193
|
+
patchable: true,
|
|
194
|
+
binaryPatched: false,
|
|
195
|
+
path: resolvedPath,
|
|
196
|
+
patchJsonPath,
|
|
197
|
+
sha256,
|
|
198
|
+
callOffset: origOffset,
|
|
199
|
+
timeAfterOffset,
|
|
200
|
+
fix: "tfx doctor --fix",
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
ok: false,
|
|
207
|
+
status: "unsupported",
|
|
208
|
+
patchable: false,
|
|
209
|
+
path: resolvedPath,
|
|
210
|
+
patchJsonPath,
|
|
211
|
+
sha256,
|
|
212
|
+
reason:
|
|
213
|
+
origOffset === -2
|
|
214
|
+
? "ambiguous-call-site-signature"
|
|
215
|
+
: "call-site-signature-not-found",
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function applyAgyPeerWakePatch({
|
|
220
|
+
agyPath,
|
|
221
|
+
version,
|
|
222
|
+
platform = process.platform,
|
|
223
|
+
arch = process.arch,
|
|
224
|
+
fs = defaultFs,
|
|
225
|
+
execFileSyncFn = execFileSync,
|
|
226
|
+
} = {}) {
|
|
227
|
+
const state = inspectAgyPeerWakePatch({ agyPath, platform, arch, fs });
|
|
228
|
+
if (state.status === "patched" || state.status === "skipped") {
|
|
229
|
+
return { ...state, changed: false };
|
|
230
|
+
}
|
|
231
|
+
if (!state.patchable) {
|
|
232
|
+
return { ...state, ok: false, changed: false };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
let resolvedVersion = version || "";
|
|
236
|
+
if (!resolvedVersion) {
|
|
237
|
+
try {
|
|
238
|
+
const out = String(
|
|
239
|
+
execFileSyncFn(state.path, ["--version"], {
|
|
240
|
+
encoding: "utf8",
|
|
241
|
+
timeout: 5000,
|
|
242
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
243
|
+
}) || "",
|
|
244
|
+
);
|
|
245
|
+
resolvedVersion = out.match(/\b(\d+\.\d+\.\d+)\b/)?.[1] || "unknown";
|
|
246
|
+
} catch {
|
|
247
|
+
resolvedVersion = "unknown";
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
let finalSha256 = state.sha256;
|
|
252
|
+
let backupEntry = null;
|
|
253
|
+
if (state.status === "unpatched") {
|
|
254
|
+
const rawBuf = fs.readFileSync(state.path);
|
|
255
|
+
const currentSha256 = createHash("sha256").update(rawBuf).digest("hex");
|
|
256
|
+
if (currentSha256 !== state.sha256) {
|
|
257
|
+
throw new Error("agy binary changed during patch preparation");
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
let backupKey = `${resolvedVersion}-orig`;
|
|
261
|
+
let backupPath = `${state.path}.bak-${backupKey}`;
|
|
262
|
+
if (fs.existsSync(backupPath)) {
|
|
263
|
+
const existingBackupSha = createHash("sha256")
|
|
264
|
+
.update(fs.readFileSync(backupPath))
|
|
265
|
+
.digest("hex");
|
|
266
|
+
if (existingBackupSha !== state.sha256) {
|
|
267
|
+
backupKey = `${resolvedVersion}-orig-${state.sha256.slice(0, 8)}`;
|
|
268
|
+
backupPath = `${state.path}.bak-${backupKey}`;
|
|
269
|
+
if (fs.existsSync(backupPath)) {
|
|
270
|
+
const suffixBackupSha = createHash("sha256")
|
|
271
|
+
.update(fs.readFileSync(backupPath))
|
|
272
|
+
.digest("hex");
|
|
273
|
+
if (suffixBackupSha !== state.sha256) {
|
|
274
|
+
throw new Error(`agy backup conflict: ${backupPath}`);
|
|
275
|
+
}
|
|
276
|
+
} else {
|
|
277
|
+
fs.copyFileSync(state.path, backupPath);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
} else {
|
|
281
|
+
fs.copyFileSync(state.path, backupPath);
|
|
282
|
+
}
|
|
283
|
+
backupEntry = { key: backupKey, path: backupPath, sha256: state.sha256 };
|
|
284
|
+
|
|
285
|
+
const buf = Buffer.from(rawBuf);
|
|
286
|
+
const expectedBl = encodeArm64Bl(
|
|
287
|
+
state.callOffset + AGY_PEER_WAKE_PATCH_PREFIX.length,
|
|
288
|
+
state.timeAfterOffset,
|
|
289
|
+
);
|
|
290
|
+
AGY_PEER_WAKE_PATCH_PREFIX.copy(buf, state.callOffset);
|
|
291
|
+
expectedBl.copy(buf, state.callOffset + AGY_PEER_WAKE_PATCH_PREFIX.length);
|
|
292
|
+
AGY_PEER_WAKE_PATCH_STP.copy(
|
|
293
|
+
buf,
|
|
294
|
+
state.callOffset + AGY_PEER_WAKE_STP_DELTA,
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
const tmpPath = `${state.path}.tmp-${process.pid}`;
|
|
298
|
+
fs.writeFileSync(tmpPath, buf, { mode: 0o755 });
|
|
299
|
+
try {
|
|
300
|
+
fs.chmodSync(tmpPath, 0o755);
|
|
301
|
+
execFileSyncFn("codesign", ["-f", "-s", "-", tmpPath], {
|
|
302
|
+
stdio: "ignore",
|
|
303
|
+
timeout: 30000,
|
|
304
|
+
});
|
|
305
|
+
const preRenameSha256 = createHash("sha256")
|
|
306
|
+
.update(fs.readFileSync(state.path))
|
|
307
|
+
.digest("hex");
|
|
308
|
+
if (preRenameSha256 !== state.sha256) {
|
|
309
|
+
throw new Error("agy binary changed before atomic replace");
|
|
310
|
+
}
|
|
311
|
+
fs.renameSync(tmpPath, state.path);
|
|
312
|
+
} catch (error) {
|
|
313
|
+
try {
|
|
314
|
+
fs.unlinkSync(tmpPath);
|
|
315
|
+
} catch {}
|
|
316
|
+
throw error;
|
|
317
|
+
}
|
|
318
|
+
finalSha256 = createHash("sha256")
|
|
319
|
+
.update(fs.readFileSync(state.path))
|
|
320
|
+
.digest("hex");
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
let existingMeta = {};
|
|
324
|
+
if (fs.existsSync(state.patchJsonPath)) {
|
|
325
|
+
try {
|
|
326
|
+
const parsed = JSON.parse(fs.readFileSync(state.patchJsonPath, "utf8"));
|
|
327
|
+
existingMeta =
|
|
328
|
+
parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
329
|
+
? parsed
|
|
330
|
+
: {};
|
|
331
|
+
} catch {
|
|
332
|
+
existingMeta = {};
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
const patches = Array.isArray(existingMeta.patches)
|
|
336
|
+
? [...new Set([...existingMeta.patches, "peer-wake"])]
|
|
337
|
+
: ["peer-wake"];
|
|
338
|
+
const backups = {
|
|
339
|
+
...(backupEntry
|
|
340
|
+
? {
|
|
341
|
+
[backupEntry.key]: {
|
|
342
|
+
path: backupEntry.path,
|
|
343
|
+
sha256: backupEntry.sha256,
|
|
344
|
+
},
|
|
345
|
+
}
|
|
346
|
+
: {}),
|
|
347
|
+
...(existingMeta.backups && typeof existingMeta.backups === "object"
|
|
348
|
+
? existingMeta.backups
|
|
349
|
+
: {}),
|
|
350
|
+
};
|
|
351
|
+
const nextMeta = {
|
|
352
|
+
...existingMeta,
|
|
353
|
+
version:
|
|
354
|
+
resolvedVersion !== "unknown"
|
|
355
|
+
? resolvedVersion
|
|
356
|
+
: existingMeta.version || "unknown",
|
|
357
|
+
sha256: finalSha256,
|
|
358
|
+
patches,
|
|
359
|
+
appliedAt: new Date().toISOString(),
|
|
360
|
+
backups,
|
|
361
|
+
};
|
|
362
|
+
fs.writeFileSync(
|
|
363
|
+
state.patchJsonPath,
|
|
364
|
+
`${JSON.stringify(nextMeta, null, 2)}\n`,
|
|
365
|
+
"utf8",
|
|
366
|
+
);
|
|
367
|
+
|
|
368
|
+
return {
|
|
369
|
+
ok: true,
|
|
370
|
+
status: "patched",
|
|
371
|
+
changed: true,
|
|
372
|
+
binaryPatched: true,
|
|
373
|
+
previousStatus: state.status,
|
|
374
|
+
path: state.path,
|
|
375
|
+
patchJsonPath: state.patchJsonPath,
|
|
376
|
+
sha256: finalSha256,
|
|
377
|
+
};
|
|
378
|
+
}
|
|
2
379
|
|
|
3
380
|
export function commandExists(name) {
|
|
4
381
|
try {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { copyFileSync, existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
|
+
import { normalizeGeminiProfileName } from "./model-effort.mjs";
|
|
3
4
|
import {
|
|
4
5
|
AGY_FAMILY_PROFILES,
|
|
5
6
|
modelFamilyPaths,
|
|
@@ -17,11 +18,7 @@ const DEFAULT_GEMINI_PROFILES = {
|
|
|
17
18
|
),
|
|
18
19
|
};
|
|
19
20
|
|
|
20
|
-
export
|
|
21
|
-
const raw = String(name ?? "").trim();
|
|
22
|
-
// Flash 가족은 뺐다. 옛 Flash 이름은 effort 와 상관없이 더 빠른 Argon Low 로 보낸다.
|
|
23
|
-
return raw.replace(/^flash\d*(?:_(?:low|high))?$/, "argon_low");
|
|
24
|
-
}
|
|
21
|
+
export { normalizeGeminiProfileName };
|
|
25
22
|
|
|
26
23
|
// ── 용도별 effort (SSOT) ──
|
|
27
24
|
// Argon 가족 안에서 역할에 따라 effort 를 나눈다. 키는 tfx 에이전트/역할 이름
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { readFileSync } from "node:fs";
|
|
6
6
|
import process from "node:process";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { isJevEnabled, judgeMcpSelection } from "@triflux/core/hub/lib/jev-addon.mjs";
|
|
8
9
|
import { CORE_SERVERS, readManifest } from "./mcp-manifest.mjs";
|
|
9
10
|
import {
|
|
10
11
|
DOMAIN_TAG_KEYWORDS,
|
|
@@ -639,7 +640,11 @@ export function getCodexMcpConfig(options = {}) {
|
|
|
639
640
|
if (registeredServers.length === 0) {
|
|
640
641
|
return { mcp_servers: {} };
|
|
641
642
|
}
|
|
642
|
-
const allowedServers = new Set(
|
|
643
|
+
const allowedServers = new Set(
|
|
644
|
+
Array.isArray(options.allowedServers)
|
|
645
|
+
? parseAvailableServers(options.allowedServers)
|
|
646
|
+
: resolveAllowedServers(options),
|
|
647
|
+
);
|
|
643
648
|
const resolvedProfile = resolveMcpProfile(
|
|
644
649
|
options.agentType,
|
|
645
650
|
options.requestedProfile,
|
|
@@ -731,9 +736,17 @@ export function buildMcpPolicy(options = {}) {
|
|
|
731
736
|
const hint = buildPromptHint(resolvedOptions);
|
|
732
737
|
|
|
733
738
|
// 플러그인 서버는 transport 설정이 없어 CLI override를 받으면 부팅이 실패한다.
|
|
739
|
+
const configServers = codexServersFromConfig(options.codexConfig);
|
|
740
|
+
const registeredServers =
|
|
741
|
+
configServers.length > 0
|
|
742
|
+
? configServers
|
|
743
|
+
: options.registeredServers !== undefined
|
|
744
|
+
? parseAvailableServers(options.registeredServers)
|
|
745
|
+
: [];
|
|
734
746
|
const codexOptions = {
|
|
735
747
|
...resolvedOptions,
|
|
736
|
-
availableServers:
|
|
748
|
+
availableServers: registeredServers,
|
|
749
|
+
allowedServers,
|
|
737
750
|
};
|
|
738
751
|
|
|
739
752
|
return {
|
|
@@ -749,6 +762,74 @@ export function buildMcpPolicy(options = {}) {
|
|
|
749
762
|
};
|
|
750
763
|
}
|
|
751
764
|
|
|
765
|
+
export async function buildMcpPolicyWithJev(options = {}, jevOptions = {}) {
|
|
766
|
+
const basePolicy = buildMcpPolicy(options);
|
|
767
|
+
if (!isJevEnabled(jevOptions)) return basePolicy;
|
|
768
|
+
const taskText = String(options.taskText || "").trim();
|
|
769
|
+
if (!taskText) return basePolicy;
|
|
770
|
+
|
|
771
|
+
const isAutoProfile =
|
|
772
|
+
!options.requestedProfile || options.requestedProfile === "auto";
|
|
773
|
+
if (!isAutoProfile && options.requestedProfile === "none") {
|
|
774
|
+
return basePolicy;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
const candidateServers = basePolicy.allowedServers;
|
|
778
|
+
if (candidateServers.length === 0) return basePolicy;
|
|
779
|
+
|
|
780
|
+
const selection = await judgeMcpSelection(
|
|
781
|
+
{
|
|
782
|
+
taskText,
|
|
783
|
+
agentType: options.agentType || basePolicy.resolvedProfile,
|
|
784
|
+
availableServers: candidateServers,
|
|
785
|
+
},
|
|
786
|
+
jevOptions,
|
|
787
|
+
);
|
|
788
|
+
if (!selection || !Array.isArray(selection.neededMcpServers))
|
|
789
|
+
return basePolicy;
|
|
790
|
+
|
|
791
|
+
const narrowedAvailable = selection.neededMcpServers.filter((server) =>
|
|
792
|
+
basePolicy.allowedServers.includes(server),
|
|
793
|
+
);
|
|
794
|
+
if (narrowedAvailable.length === 0) {
|
|
795
|
+
return {
|
|
796
|
+
...buildMcpPolicy({
|
|
797
|
+
...options,
|
|
798
|
+
requestedProfile: "none",
|
|
799
|
+
registeredServers:
|
|
800
|
+
options.registeredServers ??
|
|
801
|
+
(options.codexConfig === undefined
|
|
802
|
+
? options.availableServers
|
|
803
|
+
: undefined),
|
|
804
|
+
}),
|
|
805
|
+
requestedProfile: options.requestedProfile || "auto",
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
const nextProfile =
|
|
810
|
+
isAutoProfile &&
|
|
811
|
+
selection.mcpProfile &&
|
|
812
|
+
selection.mcpProfile in PROFILE_DEFINITIONS
|
|
813
|
+
? selection.mcpProfile
|
|
814
|
+
: options.requestedProfile;
|
|
815
|
+
|
|
816
|
+
const resolved = buildMcpPolicy({
|
|
817
|
+
...options,
|
|
818
|
+
requestedProfile: nextProfile,
|
|
819
|
+
availableServers: narrowedAvailable,
|
|
820
|
+
registeredServers:
|
|
821
|
+
options.registeredServers ??
|
|
822
|
+
(options.codexConfig === undefined
|
|
823
|
+
? options.availableServers
|
|
824
|
+
: undefined),
|
|
825
|
+
});
|
|
826
|
+
return {
|
|
827
|
+
...resolved,
|
|
828
|
+
enabledServers: resolved.allowedServers,
|
|
829
|
+
requestedProfile: options.requestedProfile || "auto",
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
|
|
752
833
|
function shellEscape(value) {
|
|
753
834
|
return `'${String(value).replace(/'/g, `'"'"'`)}'`;
|
|
754
835
|
}
|
|
@@ -863,7 +944,9 @@ async function runCli(argv = process.argv.slice(2)) {
|
|
|
863
944
|
|
|
864
945
|
let policy;
|
|
865
946
|
try {
|
|
866
|
-
policy =
|
|
947
|
+
policy = isJevEnabled()
|
|
948
|
+
? await buildMcpPolicyWithJev(args)
|
|
949
|
+
: buildMcpPolicy(args);
|
|
867
950
|
} catch (error) {
|
|
868
951
|
console.error(
|
|
869
952
|
`[mcp-filter] ${error instanceof Error ? error.message : String(error)}`,
|