@triflux/core 10.50.2 → 10.51.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/hooks/agy-session-hook.mjs +11 -56
- package/hooks/codex-session-hook.mjs +11 -101
- package/hub/bridge.mjs +1 -79
- package/hub/cli-adapter-base.mjs +12 -90
- package/hub/codex-adapter.mjs +0 -1
- package/hub/codex-preflight.mjs +5 -28
- package/hub/index.mjs +1 -1
- package/hub/lib/known-errors.json +2 -2
- package/hub/lib/process-utils.mjs +0 -123
- package/hub/lib/spawn-trace.mjs +3 -2
- package/hub/lib/timeout-defaults.mjs +0 -1
- package/hub/role-contract.mjs +0 -2
- package/hud/cli-policy.mjs +0 -22
- package/hud/constants.mjs +11 -26
- package/hud/hud-qos-status.mjs +23 -51
- package/hud/providers/gemini.mjs +105 -20
- package/hud/renderers.mjs +50 -104
- package/hud/utils.mjs +2 -2
- package/package.json +1 -3
- package/scripts/lib/agent-route-policy.mjs +162 -14
- package/scripts/lib/claudemd-scanner.mjs +2 -199
- package/scripts/lib/cli-claude.mjs +0 -2
- package/scripts/lib/context.mjs +3 -19
- package/scripts/lib/env-probe.mjs +5 -64
- package/scripts/lib/gemini-profiles.mjs +2 -69
- package/scripts/lib/machine-profile.mjs +3 -17
- package/scripts/lib/mcp-filter.mjs +109 -53
- package/scripts/lib/mcp-health.mjs +2 -2
- package/scripts/lib/mcp-server-catalog.mjs +2 -2
- package/scripts/lib/psmux-info.mjs +11 -59
- package/scripts/lib/session-presence.mjs +1 -1
- package/scripts/lib/skill-template.mjs +0 -245
- package/hub/dynamic-routing-engine.mjs +0 -511
- package/hub/lib/risk-tier.mjs +0 -53
- package/hub/lib/ssh-command.mjs +0 -231
- package/hub/lib/staged-file-detect.mjs +0 -43
- package/hub/lib/tfx-route-args.mjs +0 -353
- package/hub/routing-snapshot.mjs +0 -261
- package/mesh/index.mjs +0 -69
- package/mesh/mesh-budget.mjs +0 -137
- package/mesh/mesh-heartbeat.mjs +0 -100
- package/mesh/mesh-protocol.mjs +0 -96
- package/mesh/mesh-queue.mjs +0 -163
- package/mesh/mesh-registry.mjs +0 -78
- package/mesh/mesh-router.mjs +0 -77
- package/scripts/lib/dynamic-route-cli.mjs +0 -107
|
@@ -1,114 +1,6 @@
|
|
|
1
|
-
import { existsSync
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
|
-
import {
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
|
|
6
|
-
export const TFX_START = "<!-- TFX:START -->";
|
|
7
|
-
export const TFX_END = "<!-- TFX:END -->";
|
|
8
|
-
export const OMC_END = "<!-- OMC:END -->";
|
|
9
|
-
const TFX_VERSION_RE = /<!-- TFX:VERSION:([\d.]+) -->/u;
|
|
10
|
-
|
|
11
|
-
const LEGACY_PATTERNS = [
|
|
12
|
-
/<user_cli_routing>/u,
|
|
13
|
-
/Codex Pro 무료 기간/u,
|
|
14
|
-
/codex exec --dangerously-bypass.*skip-git-repo-check/u,
|
|
15
|
-
/OMC 에이전트 → CLI 매핑/u,
|
|
16
|
-
/Spark 가드레일/u,
|
|
17
|
-
];
|
|
18
|
-
|
|
19
|
-
const DEFAULT_TFX_TEMPLATE = [
|
|
20
|
-
"### triflux CLI routing (managed)",
|
|
21
|
-
"- 이 블록은 triflux setup에서 자동으로 관리됩니다.",
|
|
22
|
-
"- 직접 수정이 필요하면 블록 바깥에 사용자 섹션을 추가하세요.",
|
|
23
|
-
].join("\n");
|
|
24
|
-
|
|
25
|
-
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
|
|
26
|
-
const DEFAULT_TEMPLATE_PATH = join(
|
|
27
|
-
SCRIPT_DIR,
|
|
28
|
-
"..",
|
|
29
|
-
"templates",
|
|
30
|
-
"claudemd-tfx-section.md",
|
|
31
|
-
);
|
|
32
|
-
|
|
33
|
-
function resolveVersion(version) {
|
|
34
|
-
if (version) return version;
|
|
35
|
-
try {
|
|
36
|
-
const pkgPath = join(SCRIPT_DIR, "..", "..", "package.json");
|
|
37
|
-
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
38
|
-
return pkg.version || "0.0.0";
|
|
39
|
-
} catch {
|
|
40
|
-
return "0.0.0";
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function resolveTemplate(template, templatePath = DEFAULT_TEMPLATE_PATH) {
|
|
45
|
-
if (typeof template === "string" && template.trim()) {
|
|
46
|
-
return template.trim();
|
|
47
|
-
}
|
|
48
|
-
if (existsSync(templatePath)) {
|
|
49
|
-
const raw = readFileSync(templatePath, "utf8").trim();
|
|
50
|
-
if (raw) return raw;
|
|
51
|
-
}
|
|
52
|
-
return DEFAULT_TFX_TEMPLATE;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function findManagedSection(rawText) {
|
|
56
|
-
const raw = String(rawText || "");
|
|
57
|
-
const startIdx = raw.indexOf(TFX_START);
|
|
58
|
-
const endMarkerIdx = raw.indexOf(TFX_END);
|
|
59
|
-
if (startIdx === -1 || endMarkerIdx === -1 || endMarkerIdx <= startIdx) {
|
|
60
|
-
return {
|
|
61
|
-
found: false,
|
|
62
|
-
content: null,
|
|
63
|
-
version: null,
|
|
64
|
-
startIdx: -1,
|
|
65
|
-
endIdx: -1,
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const endIdx = endMarkerIdx + TFX_END.length;
|
|
70
|
-
const content = raw.slice(startIdx, endIdx);
|
|
71
|
-
const versionMatch = content.match(TFX_VERSION_RE);
|
|
72
|
-
|
|
73
|
-
return {
|
|
74
|
-
found: true,
|
|
75
|
-
content,
|
|
76
|
-
version: versionMatch ? versionMatch[1] : null,
|
|
77
|
-
startIdx,
|
|
78
|
-
endIdx,
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function detectLegacyRange(rawText) {
|
|
83
|
-
const raw = String(rawText || "");
|
|
84
|
-
const matches = LEGACY_PATTERNS.some((pattern) => pattern.test(raw));
|
|
85
|
-
if (!matches) {
|
|
86
|
-
return { found: false, startIdx: -1, endIdx: -1 };
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const startTag = raw.indexOf("<user_cli_routing>");
|
|
90
|
-
const endTag = raw.indexOf("</user_cli_routing>");
|
|
91
|
-
if (startTag === -1 || endTag === -1 || endTag <= startTag) {
|
|
92
|
-
return { found: true, startIdx: -1, endIdx: -1 };
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
let removeStart = startTag;
|
|
96
|
-
const userOverridesComment = "<!-- USER OVERRIDES";
|
|
97
|
-
const commentIdx = raw.lastIndexOf(userOverridesComment, startTag);
|
|
98
|
-
if (commentIdx !== -1 && startTag - commentIdx < 200) {
|
|
99
|
-
removeStart = commentIdx;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return {
|
|
103
|
-
found: true,
|
|
104
|
-
startIdx: removeStart,
|
|
105
|
-
endIdx: endTag + "</user_cli_routing>".length,
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function normalizeSpacing(text) {
|
|
110
|
-
return String(text || "").replace(/\n{3,}/gu, "\n\n");
|
|
111
|
-
}
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
112
4
|
|
|
113
5
|
export function findAllClaudeMdPaths(options = {}) {
|
|
114
6
|
const cwd = options.cwd ? resolve(options.cwd) : process.cwd();
|
|
@@ -132,92 +24,3 @@ export function findAllClaudeMdPaths(options = {}) {
|
|
|
132
24
|
}
|
|
133
25
|
return paths;
|
|
134
26
|
}
|
|
135
|
-
|
|
136
|
-
export function writeSection(filePath, options = {}) {
|
|
137
|
-
const absolutePath = resolve(filePath);
|
|
138
|
-
const version = resolveVersion(options.version);
|
|
139
|
-
const template = resolveTemplate(options.template, options.templatePath);
|
|
140
|
-
const block = [
|
|
141
|
-
TFX_START,
|
|
142
|
-
`<!-- TFX:VERSION:${version} -->`,
|
|
143
|
-
template,
|
|
144
|
-
TFX_END,
|
|
145
|
-
].join("\n");
|
|
146
|
-
|
|
147
|
-
if (!existsSync(absolutePath)) {
|
|
148
|
-
writeFileSync(absolutePath, `${block}\n`, "utf8");
|
|
149
|
-
return { action: "created", version, path: absolutePath };
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const raw = readFileSync(absolutePath, "utf8");
|
|
153
|
-
const existing = findManagedSection(raw);
|
|
154
|
-
|
|
155
|
-
if (existing.found) {
|
|
156
|
-
const nextContent = `${raw.slice(0, existing.startIdx)}${block}${raw.slice(existing.endIdx)}`;
|
|
157
|
-
writeFileSync(absolutePath, nextContent, "utf8");
|
|
158
|
-
return {
|
|
159
|
-
action: "updated",
|
|
160
|
-
oldVersion: existing.version,
|
|
161
|
-
version,
|
|
162
|
-
path: absolutePath,
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const omcEndIdx = raw.indexOf(OMC_END);
|
|
167
|
-
if (omcEndIdx !== -1) {
|
|
168
|
-
const insertAt = omcEndIdx + OMC_END.length;
|
|
169
|
-
const before = raw.slice(0, insertAt);
|
|
170
|
-
const after = raw.slice(insertAt);
|
|
171
|
-
const nextContent = `${before}\n${block}${after}`;
|
|
172
|
-
writeFileSync(absolutePath, nextContent, "utf8");
|
|
173
|
-
return { action: "inserted_after_omc", version, path: absolutePath };
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const separator = raw.endsWith("\n") ? "" : "\n";
|
|
177
|
-
writeFileSync(absolutePath, `${raw}${separator}${block}\n`, "utf8");
|
|
178
|
-
return { action: "appended", version, path: absolutePath };
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
export function migrateClaudeMd(filePath, options = {}) {
|
|
182
|
-
const absolutePath = resolve(filePath);
|
|
183
|
-
if (!existsSync(absolutePath)) {
|
|
184
|
-
return { action: "no_file", removed: [], path: absolutePath };
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const raw = readFileSync(absolutePath, "utf8");
|
|
188
|
-
const existing = findManagedSection(raw);
|
|
189
|
-
const legacy = detectLegacyRange(raw);
|
|
190
|
-
|
|
191
|
-
if (existing.found && !legacy.found) {
|
|
192
|
-
return {
|
|
193
|
-
action: "already_managed",
|
|
194
|
-
removed: [],
|
|
195
|
-
version: existing.version,
|
|
196
|
-
path: absolutePath,
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const removed = [];
|
|
201
|
-
let nextContent = raw;
|
|
202
|
-
|
|
203
|
-
if (legacy.found && legacy.startIdx !== -1 && legacy.endIdx !== -1) {
|
|
204
|
-
nextContent = `${nextContent.slice(0, legacy.startIdx)}${nextContent.slice(legacy.endIdx)}`;
|
|
205
|
-
removed.push("<user_cli_routing> block");
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
const existingAfterLegacy = findManagedSection(nextContent);
|
|
209
|
-
if (existingAfterLegacy.found) {
|
|
210
|
-
nextContent = `${nextContent.slice(0, existingAfterLegacy.startIdx)}${nextContent.slice(existingAfterLegacy.endIdx)}`;
|
|
211
|
-
removed.push("old TFX block");
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
writeFileSync(absolutePath, normalizeSpacing(nextContent), "utf8");
|
|
215
|
-
const writeResult = writeSection(absolutePath, options);
|
|
216
|
-
|
|
217
|
-
return {
|
|
218
|
-
action: "migrated",
|
|
219
|
-
removed,
|
|
220
|
-
version: writeResult.version,
|
|
221
|
-
path: absolutePath,
|
|
222
|
-
};
|
|
223
|
-
}
|
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
// 책임: Claude 는 외부 CLI 가 아닌 Claude Code Agent 도구로 dispatch 된다. plan() 은
|
|
4
4
|
// command=null 을 반환하고 호출자가 Agent() 로 라우팅하도록 신호한다.
|
|
5
5
|
//
|
|
6
|
-
// 출처: scripts/tfx-route.sh route_agent() L1062-L1063 (explore|claude),
|
|
7
|
-
// L1196-L1203 (TFX_CLI_MODE=gemini fallback), TFX_VERIFIER_OVERRIDE=claude.
|
|
8
6
|
// timeoutSec은 lane 예상 소요 시간(advisory)이다. hard ceiling 집행은 Bash route가 소유한다.
|
|
9
7
|
|
|
10
8
|
export const id = "claude";
|
package/scripts/lib/context.mjs
CHANGED
|
@@ -5,19 +5,13 @@
|
|
|
5
5
|
* 하나의 요청에서 발생한 모든 로그를 추적할 수 있다.
|
|
6
6
|
*
|
|
7
7
|
* 사용법:
|
|
8
|
-
* import {
|
|
8
|
+
* import { getCorrelationId, withRequestContext } from './lib/context.mjs';
|
|
9
9
|
*
|
|
10
10
|
* // 미들웨어에서 컨텍스트 생성
|
|
11
11
|
* withRequestContext({ method: 'POST', path: '/bridge/result' }, () => {
|
|
12
|
-
* const
|
|
13
|
-
* log.info({ agentId }, 'bridge.result_received');
|
|
12
|
+
* const correlationId = getCorrelationId();
|
|
14
13
|
* });
|
|
15
14
|
*
|
|
16
|
-
* // 내부 함수에서 자동 상관 ID
|
|
17
|
-
* function processResult() {
|
|
18
|
-
* const log = getLogger();
|
|
19
|
-
* log.info('result.processed'); // correlationId 자동 포함
|
|
20
|
-
* }
|
|
21
15
|
*/
|
|
22
16
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
23
17
|
import { randomUUID } from "node:crypto";
|
|
@@ -25,17 +19,7 @@ import { randomUUID } from "node:crypto";
|
|
|
25
19
|
import { logger } from "./logger.mjs";
|
|
26
20
|
|
|
27
21
|
/** @type {AsyncLocalStorage<{logger: import('pino').Logger, correlationId: string}>} */
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* 현재 요청 컨텍스트의 로거를 반환한다.
|
|
32
|
-
* 요청 컨텍스트 밖에서 호출하면 기본 로거를 반환한다.
|
|
33
|
-
*
|
|
34
|
-
* @returns {import('pino').Logger}
|
|
35
|
-
*/
|
|
36
|
-
export function getLogger() {
|
|
37
|
-
return asyncLocalStorage.getStore()?.logger || logger;
|
|
38
|
-
}
|
|
22
|
+
const asyncLocalStorage = new AsyncLocalStorage();
|
|
39
23
|
|
|
40
24
|
/**
|
|
41
25
|
* 현재 요청의 상관 ID를 반환한다.
|
|
@@ -1,25 +1,15 @@
|
|
|
1
|
-
import { execSync
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { existsSync, readFileSync } from "node:fs";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
|
-
import {
|
|
6
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { join } from "node:path";
|
|
7
6
|
import { resolveHubPortForContext } from "../../hub/hub-lifecycle.mjs";
|
|
8
7
|
import { whichCommand, whichCommandAsync } from "../../hub/platform.mjs";
|
|
9
8
|
|
|
10
9
|
const HUB_DEFAULT_PORT = 27888;
|
|
11
|
-
const DEFAULT_STATUS_URL = "http://127.0.0.1:27888/status";
|
|
12
|
-
const _sab = new Int32Array(new SharedArrayBuffer(4));
|
|
13
10
|
const CLI_PROBE_CACHE = new Map();
|
|
14
11
|
const CLI_PROBE_PROMISES = new Map();
|
|
15
12
|
|
|
16
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
-
const DEFAULT_PKG_ROOT = join(dirname(__filename), "..", "..");
|
|
18
|
-
|
|
19
|
-
function sleepSync(ms) {
|
|
20
|
-
Atomics.wait(_sab, 0, 0, ms);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
13
|
function fetchHubStatus({
|
|
24
14
|
execSyncFn = execSync,
|
|
25
15
|
statusUrl = resolveDefaultStatusUrl(),
|
|
@@ -38,10 +28,7 @@ function fetchHubStatus({
|
|
|
38
28
|
};
|
|
39
29
|
}
|
|
40
30
|
|
|
41
|
-
|
|
42
|
-
env = process.env,
|
|
43
|
-
cwd = process.cwd(),
|
|
44
|
-
) {
|
|
31
|
+
function resolveDefaultStatusUrl(env = process.env, cwd = process.cwd()) {
|
|
45
32
|
const port = resolveHubPortForContext({
|
|
46
33
|
env,
|
|
47
34
|
cwd,
|
|
@@ -125,7 +112,7 @@ async function resolveCliProbe(name, options = {}) {
|
|
|
125
112
|
return toCliResult(path);
|
|
126
113
|
}
|
|
127
114
|
|
|
128
|
-
|
|
115
|
+
async function checkCli(name, options = {}) {
|
|
129
116
|
const cliName = normalizeCliName(name);
|
|
130
117
|
if (!cliName) return { ok: false };
|
|
131
118
|
|
|
@@ -168,11 +155,6 @@ export async function probeClis(names, options = {}) {
|
|
|
168
155
|
return mapCliResults(cliNames, results);
|
|
169
156
|
}
|
|
170
157
|
|
|
171
|
-
export function resetCliProbeCache() {
|
|
172
|
-
CLI_PROBE_CACHE.clear();
|
|
173
|
-
CLI_PROBE_PROMISES.clear();
|
|
174
|
-
}
|
|
175
|
-
|
|
176
158
|
export function detectCodexAuthState({
|
|
177
159
|
homeDir = homedir(),
|
|
178
160
|
existsSyncFn = existsSync,
|
|
@@ -241,16 +223,9 @@ export function detectCodexPlan(options = {}) {
|
|
|
241
223
|
export function checkHub({
|
|
242
224
|
env = process.env,
|
|
243
225
|
cwd = process.cwd(),
|
|
244
|
-
pkgRoot = DEFAULT_PKG_ROOT,
|
|
245
226
|
statusUrl = resolveDefaultStatusUrl(env, cwd),
|
|
246
|
-
restart = true,
|
|
247
227
|
requestTimeoutMs = 3000,
|
|
248
|
-
pollAttempts = 8,
|
|
249
|
-
pollIntervalMs = 500,
|
|
250
228
|
execSyncFn = execSync,
|
|
251
|
-
spawnFn = spawn,
|
|
252
|
-
existsSyncFn = existsSync,
|
|
253
|
-
sleepSyncFn = sleepSync,
|
|
254
229
|
} = {}) {
|
|
255
230
|
const guardedStatusUrl = resolveStatusUrlForContext({ statusUrl, env, cwd });
|
|
256
231
|
try {
|
|
@@ -259,41 +234,7 @@ export function checkHub({
|
|
|
259
234
|
statusUrl: guardedStatusUrl,
|
|
260
235
|
timeout: requestTimeoutMs,
|
|
261
236
|
});
|
|
262
|
-
} catch {}
|
|
263
|
-
|
|
264
|
-
if (!restart) return { ok: false, state: "unreachable", restart: "disabled" };
|
|
265
|
-
|
|
266
|
-
const serverPath = join(pkgRoot, "hub", "server.mjs");
|
|
267
|
-
if (!existsSyncFn(serverPath))
|
|
268
|
-
return { ok: false, state: "unreachable", restart: "no_server" };
|
|
269
|
-
|
|
270
|
-
try {
|
|
271
|
-
const child = spawnFn(process.execPath, [serverPath], {
|
|
272
|
-
env: { ...env, TFX_HUB_PORT: String(new URL(guardedStatusUrl).port) },
|
|
273
|
-
detached: true,
|
|
274
|
-
stdio: "ignore",
|
|
275
|
-
windowsHide: true,
|
|
276
|
-
});
|
|
277
|
-
child.unref();
|
|
278
237
|
} catch {
|
|
279
|
-
return { ok: false, state: "unreachable", restart: "
|
|
238
|
+
return { ok: false, state: "unreachable", restart: "disabled" };
|
|
280
239
|
}
|
|
281
|
-
|
|
282
|
-
for (let i = 0; i < pollAttempts; i++) {
|
|
283
|
-
sleepSyncFn(pollIntervalMs);
|
|
284
|
-
try {
|
|
285
|
-
const status = fetchHubStatus({
|
|
286
|
-
execSyncFn,
|
|
287
|
-
statusUrl: guardedStatusUrl,
|
|
288
|
-
timeout: Math.min(requestTimeoutMs, 1000),
|
|
289
|
-
});
|
|
290
|
-
if (status.state === "healthy") {
|
|
291
|
-
return { ...status, restarted: true };
|
|
292
|
-
}
|
|
293
|
-
} catch {}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
return { ok: false, state: "unreachable", restart: "timeout" };
|
|
297
240
|
}
|
|
298
|
-
|
|
299
|
-
export { DEFAULT_PKG_ROOT, DEFAULT_STATUS_URL };
|
|
@@ -30,36 +30,10 @@ const DEFAULT_GEMINI_PROFILES = {
|
|
|
30
30
|
},
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
// 1회 마이그레이션 테이블: 옛 ID 형식 모델값 → agy display name.
|
|
34
|
-
// null = 카탈로그에서 빠진 모델(2.5 계열, 3 Flash) → 해당 프로필 제거 대상.
|
|
35
|
-
const LEGACY_MODEL_MIGRATION = {
|
|
36
|
-
"gemini-3.1-pro-preview": "Gemini 3.1 Pro (High)",
|
|
37
|
-
"gemini-3-flash-preview": null,
|
|
38
|
-
"Gemini 3 Flash": null,
|
|
39
|
-
"gemini-2.5-pro": null,
|
|
40
|
-
"gemini-2.5-flash": null,
|
|
41
|
-
"gemini-2.5-flash-lite": null,
|
|
42
|
-
};
|
|
43
|
-
// 과거 setup 이 자동 생성했던 프로필 이름. 정책상 더 쓰지 않으므로 제거한다.
|
|
44
|
-
// 사용자가 직접 이름 붙인 프로필은 건드리지 않는다.
|
|
45
|
-
const LEGACY_PROFILE_NAMES = [
|
|
46
|
-
"pro25",
|
|
47
|
-
"flash25",
|
|
48
|
-
"lite25",
|
|
49
|
-
"flash3",
|
|
50
|
-
"pro31",
|
|
51
|
-
"pro31_low",
|
|
52
|
-
"flash35",
|
|
53
|
-
"flash35_high",
|
|
54
|
-
"flash35_low",
|
|
55
|
-
];
|
|
56
|
-
// 과거 setup 이 자동 생성한 기본 model 값. 사용자가 고른 값이 아니므로 새 기본으로 올린다.
|
|
57
|
-
const LEGACY_DEFAULT_MODELS = new Set(["Gemini 3.5 Flash (Medium)"]);
|
|
58
|
-
|
|
59
33
|
// ── 용도별 effort (SSOT) ──
|
|
60
34
|
// 한 세대(3.8 Flash)만 쓰고 역할에 따라 effort 를 나눈다. 키는 tfx 에이전트/역할 이름
|
|
61
35
|
// (agent-map.json 의 에이전트, tfx multi --assign 의 role). bash(tfx-route.sh)와
|
|
62
|
-
// 실행
|
|
36
|
+
// 실행 모드(execution-mode)가 이 표를 읽는다.
|
|
63
37
|
// High : 판단이 결과를 좌우하는 역할 (검토, 설계, 디버깅, 분석)
|
|
64
38
|
// Medium : 생성/작성/실행 (기본값)
|
|
65
39
|
// Low : 분류, 요약, 추출, 번역처럼 짧고 기계적인 작업
|
|
@@ -182,40 +156,6 @@ function ensureGeminiProfiles({
|
|
|
182
156
|
)
|
|
183
157
|
cfg.profiles = {};
|
|
184
158
|
|
|
185
|
-
// ── 1회 마이그레이션: deprecated 2.5 프로필 prune + 옛 ID 형식 → display name ──
|
|
186
|
-
// merge-only 로직만으로는 기존 사용자 파일에 남은 stale 프로필/옛 모델 ID 가
|
|
187
|
-
// 정리되지 않으므로, 신규 default 를 채우기 전에 마이그레이션을 먼저 적용한다.
|
|
188
|
-
let migrated = false;
|
|
189
|
-
for (const legacy of LEGACY_PROFILE_NAMES) {
|
|
190
|
-
if (cfg.profiles[legacy]) {
|
|
191
|
-
delete cfg.profiles[legacy];
|
|
192
|
-
migrated = true;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
for (const [pname, pval] of Object.entries(cfg.profiles)) {
|
|
196
|
-
const mid = typeof pval === "string" ? pval : pval?.model;
|
|
197
|
-
if (mid && Object.hasOwn(LEGACY_MODEL_MIGRATION, mid)) {
|
|
198
|
-
const repl = LEGACY_MODEL_MIGRATION[mid];
|
|
199
|
-
if (repl === null) {
|
|
200
|
-
delete cfg.profiles[pname];
|
|
201
|
-
} else if (typeof pval === "string") {
|
|
202
|
-
cfg.profiles[pname] = repl;
|
|
203
|
-
} else {
|
|
204
|
-
cfg.profiles[pname].model = repl;
|
|
205
|
-
}
|
|
206
|
-
migrated = true;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
if (
|
|
210
|
-
typeof cfg.model === "string" &&
|
|
211
|
-
(Object.hasOwn(LEGACY_MODEL_MIGRATION, cfg.model) ||
|
|
212
|
-
LEGACY_DEFAULT_MODELS.has(cfg.model))
|
|
213
|
-
) {
|
|
214
|
-
// 옛 ID 형식/이전 세대 자동 생성 기본값은 새 기본(DEFAULT)으로 정규화한다.
|
|
215
|
-
cfg.model = DEFAULT_GEMINI_PROFILES.model;
|
|
216
|
-
migrated = true;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
159
|
let added = 0;
|
|
220
160
|
for (const [name, value] of Object.entries(
|
|
221
161
|
DEFAULT_GEMINI_PROFILES.profiles,
|
|
@@ -227,12 +167,7 @@ function ensureGeminiProfiles({
|
|
|
227
167
|
}
|
|
228
168
|
if (!cfg.model) cfg.model = DEFAULT_GEMINI_PROFILES.model;
|
|
229
169
|
|
|
230
|
-
if (added > 0
|
|
231
|
-
if (migrated) {
|
|
232
|
-
try {
|
|
233
|
-
copyFileSync(profilesPath, profilesPath + `.bak.${Date.now()}`);
|
|
234
|
-
} catch {}
|
|
235
|
-
}
|
|
170
|
+
if (added > 0) {
|
|
236
171
|
writeFileSync(profilesPath, JSON.stringify(cfg, null, 2) + "\n", {
|
|
237
172
|
encoding: "utf8",
|
|
238
173
|
mode: 0o600,
|
|
@@ -260,8 +195,6 @@ function ensureGeminiProfiles({
|
|
|
260
195
|
export {
|
|
261
196
|
DEFAULT_GEMINI_PROFILES,
|
|
262
197
|
ensureGeminiProfiles,
|
|
263
|
-
GEMINI_DEFAULT_PURPOSE_PROFILE,
|
|
264
|
-
GEMINI_PROFILE_BY_PURPOSE,
|
|
265
198
|
resolveGeminiModel,
|
|
266
199
|
resolveGeminiProfileForPurpose,
|
|
267
200
|
};
|
|
@@ -14,7 +14,7 @@ import { readFileSync } from "node:fs";
|
|
|
14
14
|
import { homedir } from "node:os";
|
|
15
15
|
import { join, resolve } from "node:path";
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
const MACHINE_PROFILE_FILENAME = "machine-profile.env";
|
|
18
18
|
|
|
19
19
|
export const MACHINE_PROFILE_KEYS = Object.freeze([
|
|
20
20
|
"TFX_MACHINE_PROFILE_VERSION",
|
|
@@ -28,9 +28,9 @@ export const MACHINE_PROFILE_KEYS = Object.freeze([
|
|
|
28
28
|
"TFX_STALL_KILL",
|
|
29
29
|
]);
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
const MACHINE_PROFILE_KEY_SET = new Set(MACHINE_PROFILE_KEYS);
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
const MACHINE_PROFILE_VALUE_RE = /^[A-Za-z0-9_.-]+$/u;
|
|
34
34
|
|
|
35
35
|
// CLI 이름과 disable 키의 대응. 이 두 개가 TFX_DISABLE_* SSOT 의 전부다.
|
|
36
36
|
const CLI_DISABLE_KEYS = Object.freeze({
|
|
@@ -248,20 +248,6 @@ export function normalizeCliName(cli) {
|
|
|
248
248
|
return CLI_ALIASES[key] || null;
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
-
/**
|
|
252
|
-
* 세션 문맥 주입용 한 줄. 형식은 고정이며 소비자가 파싱하지 않고 그대로 보여준다.
|
|
253
|
-
*
|
|
254
|
-
* @param {ReturnType<typeof resolveCliPolicy>} policy
|
|
255
|
-
* @returns {string}
|
|
256
|
-
*/
|
|
257
|
-
export function formatCliPolicyLine(policy) {
|
|
258
|
-
const codex = policy?.codexDisabled ? "off" : "on";
|
|
259
|
-
const antigravity = policy?.antigravityDisabled ? "off" : "on";
|
|
260
|
-
const codexSource = policy?.source?.codex || "default";
|
|
261
|
-
const antigravitySource = policy?.source?.antigravity || "default";
|
|
262
|
-
return `cli-policy: codex=${codex} antigravity=${antigravity} (SSOT: TFX_DISABLE_*; codex<-${codexSource}, antigravity<-${antigravitySource})`;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
251
|
/**
|
|
266
252
|
* 차단된 CLI 실행 요청에 쓸 오류를 만든다. throw 는 호출자가 한다. 정책상 허용된
|
|
267
253
|
* CLI 이거나 모르는 이름이면 null 을 준다.
|