@triflux/remote 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/cto/brief.mjs +0 -12
- package/cto/collect.mjs +0 -73
- package/cto/current.schema.json +0 -4
- package/cto/status.mjs +2 -50
- package/hub/index.mjs +0 -8
- package/hub/team/agent-map.json +0 -1
- package/hub/team/ansi.mjs +1 -35
- package/hub/team/backend.mjs +9 -145
- package/hub/team/cli/commands/attach.mjs +1 -17
- package/hub/team/cli/commands/control.mjs +12 -38
- package/hub/team/cli/commands/debug.mjs +1 -37
- package/hub/team/cli/commands/focus.mjs +5 -31
- package/hub/team/cli/commands/interrupt.mjs +6 -25
- package/hub/team/cli/commands/kill.mjs +1 -30
- package/hub/team/cli/commands/list.mjs +0 -16
- package/hub/team/cli/commands/send.mjs +6 -26
- package/hub/team/cli/commands/start/index.mjs +41 -133
- package/hub/team/cli/commands/start/parse-args.mjs +15 -40
- package/hub/team/cli/commands/start/start-headless.mjs +138 -105
- package/hub/team/cli/commands/start/start-mux.mjs +0 -3
- package/hub/team/cli/commands/status.mjs +2 -57
- package/hub/team/cli/commands/stop.mjs +14 -19
- package/hub/team/cli/help.mjs +4 -5
- package/hub/team/cli/manifest.mjs +5 -1
- package/hub/team/cli/render.mjs +0 -12
- package/hub/team/cli/services/hub-client.mjs +2 -88
- package/hub/team/cli/services/runtime-mode.mjs +27 -98
- package/hub/team/handoff.mjs +3 -34
- package/hub/team/headless.mjs +13 -754
- package/hub/team/orchestrator.mjs +4 -30
- package/hub/team/pane.mjs +6 -28
- package/hub/team/psmux.mjs +0 -1
- package/hub/team/remote-session.mjs +1 -2
- package/hub/team/session.mjs +7 -91
- package/hub/team/swarm-intent.mjs +0 -198
- package/hub/team/tui-core.mjs +1 -1
- package/hub/team/tui-viewer.mjs +2 -2
- package/hub/team/tui.mjs +8 -269
- package/hub/team/wt-manager.mjs +2 -2
- package/hub/workers/factory.mjs +4 -37
- package/hub/workers/lib/jsonrpc-ws-uds.mjs +0 -2
- package/package.json +1 -1
- 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/skill-template.mjs +0 -245
- package/hub/team/build-worker-prompt.mjs +0 -92
- package/hub/team/check-mcp-hub.mjs +0 -49
- package/hub/team/claude-native-bridge.mjs +0 -987
- package/hub/team/cli/commands/start/start-in-process.mjs +0 -50
- package/hub/team/cli/commands/start/start-wt.mjs +0 -86
- package/hub/team/cli/services/native-control.mjs +0 -145
- package/hub/team/codex-review.mjs +0 -520
- package/hub/team/conductor-mesh-bridge.mjs +0 -122
- package/hub/team/conductor.mjs +0 -1529
- package/hub/team/consensus-meta.mjs +0 -176
- package/hub/team/cto-pull.mjs +0 -31
- package/hub/team/daemon-pty-tmux-bridge.mjs +0 -146
- package/hub/team/dashboard.mjs +0 -322
- package/hub/team/event-log.mjs +0 -84
- package/hub/team/execution-mode.mjs +0 -324
- package/hub/team/extract-completion-payload.mjs +0 -110
- package/hub/team/headless-bridge-session.mjs +0 -17
- package/hub/team/health-probe.mjs +0 -435
- package/hub/team/interactive-native-launcher.mjs +0 -119
- package/hub/team/interactive-native-worker.mjs +0 -80
- package/hub/team/interactive-tui-transport.mjs +0 -479
- package/hub/team/launcher-template.mjs +0 -144
- package/hub/team/mcp-selector.mjs +0 -145
- package/hub/team/native-supervisor.mjs +0 -460
- package/hub/team/native.mjs +0 -727
- package/hub/team/recovery-store.mjs +0 -149
- package/hub/team/remote-probe.mjs +0 -292
- package/hub/team/remote-watcher.mjs +0 -506
- package/hub/team/runtime-strategy.mjs +0 -200
- package/hub/team/schemas/worker-completion.json +0 -39
- package/hub/team/sentinel-capture.mjs +0 -127
- package/hub/team/swarm-cli.mjs +0 -390
- package/hub/team/swarm-hypervisor.mjs +0 -2206
- package/hub/team/swarm-locks.mjs +0 -335
- package/hub/team/swarm-planner.mjs +0 -371
- package/hub/team/swarm-preflight.mjs +0 -366
- package/hub/team/swarm-reconciler.mjs +0 -242
- package/hub/team/tui-remote-adapter.mjs +0 -377
- package/hub/team/worker-completion-validator.mjs +0 -79
- package/hub/team/worktree-lifecycle.mjs +0 -639
- package/hub/team/wt-templates.json +0 -43
- package/hub/workers/antigravity-route-worker.mjs +0 -179
- package/scripts/lib/dynamic-route-cli.mjs +0 -107
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
// Canonical Codex role policy. Concrete model IDs stay in profile config files.
|
|
2
2
|
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
3
5
|
import {
|
|
4
6
|
normalizeCodexProfileName,
|
|
5
7
|
resolveCodexProfileConfigValues,
|
|
6
8
|
} from "./codex-profile-config.mjs";
|
|
9
|
+
import { resolveGeminiProfileForPurpose } from "./gemini-profiles.mjs";
|
|
7
10
|
|
|
8
11
|
const policy = {
|
|
9
12
|
executor: {
|
|
@@ -198,7 +201,7 @@ function freezePolicy(entry) {
|
|
|
198
201
|
return Object.freeze({ ...entry });
|
|
199
202
|
}
|
|
200
203
|
|
|
201
|
-
|
|
204
|
+
const CODEX_AGENT_POLICY = Object.freeze(
|
|
202
205
|
Object.fromEntries(
|
|
203
206
|
Object.entries(policy).map(([agent, entry]) => [
|
|
204
207
|
agent,
|
|
@@ -206,9 +209,9 @@ export const CODEX_AGENT_POLICY = Object.freeze(
|
|
|
206
209
|
]),
|
|
207
210
|
),
|
|
208
211
|
);
|
|
209
|
-
|
|
212
|
+
const DEFAULT_CODEX_AGENT = "executor";
|
|
210
213
|
|
|
211
|
-
|
|
214
|
+
const CANONICAL_CODEX_PROFILE_OVERRIDES = Object.freeze(
|
|
212
215
|
new Set([
|
|
213
216
|
"gpt6_luna_low",
|
|
214
217
|
"gpt6_luna_high",
|
|
@@ -219,7 +222,7 @@ export const CANONICAL_CODEX_PROFILE_OVERRIDES = Object.freeze(
|
|
|
219
222
|
"gpt6_astra_ultra",
|
|
220
223
|
]),
|
|
221
224
|
);
|
|
222
|
-
|
|
225
|
+
const ULTRA_ELIGIBLE_CODEX_AGENTS = Object.freeze(
|
|
223
226
|
new Set(["deep-executor", "scientist-deep"]),
|
|
224
227
|
);
|
|
225
228
|
|
|
@@ -316,31 +319,176 @@ export function resolveNestedCodexAgentProfile(
|
|
|
316
319
|
return effort.toLowerCase() === "ultra" ? "gpt6_astra_max" : candidate;
|
|
317
320
|
}
|
|
318
321
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
322
|
+
const MIN_TIMEOUT_BY_AGENT = Object.freeze({
|
|
323
|
+
"deep-executor": 900,
|
|
324
|
+
architect: 900,
|
|
325
|
+
planner: 900,
|
|
326
|
+
critic: 900,
|
|
327
|
+
analyst: 900,
|
|
328
|
+
"document-specialist": 900,
|
|
329
|
+
scientist: 900,
|
|
330
|
+
"scientist-deep": 900,
|
|
331
|
+
"code-reviewer": 600,
|
|
332
|
+
"security-reviewer": 600,
|
|
333
|
+
"quality-reviewer": 600,
|
|
334
|
+
executor: 300,
|
|
335
|
+
debugger: 300,
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
const EXPECTED_DURATION_BY_AGENT = Object.freeze({
|
|
339
|
+
writer: 90,
|
|
340
|
+
verifier: 90,
|
|
341
|
+
"qa-tester": 90,
|
|
342
|
+
executor: 300,
|
|
343
|
+
debugger: 300,
|
|
344
|
+
"test-engineer": 300,
|
|
345
|
+
"code-reviewer": 600,
|
|
346
|
+
"security-reviewer": 600,
|
|
347
|
+
architect: 600,
|
|
348
|
+
planner: 600,
|
|
349
|
+
critic: 600,
|
|
350
|
+
analyst: 600,
|
|
351
|
+
scientist: 900,
|
|
352
|
+
"scientist-deep": 900,
|
|
353
|
+
"deep-executor": 900,
|
|
354
|
+
"document-specialist": 900,
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
const NATIVE_CODEX_AGENTS = new Set([
|
|
358
|
+
"explore",
|
|
359
|
+
"verifier",
|
|
360
|
+
"test-engineer",
|
|
361
|
+
"qa-tester",
|
|
362
|
+
]);
|
|
363
|
+
|
|
364
|
+
function resolveDirectCodexRoutePolicy(agent) {
|
|
365
|
+
if (agent === "explore") {
|
|
366
|
+
return { ...CODEX_AGENT_POLICY.spark, timeoutSec: 600, mcpHint: "analyze" };
|
|
367
|
+
}
|
|
368
|
+
if (agent === "antigravity" || agent === "agy") {
|
|
369
|
+
return CODEX_AGENT_POLICY.designer;
|
|
370
|
+
}
|
|
371
|
+
return resolveCodexAgentPolicy(agent);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export function resolveAgentRoute(
|
|
375
|
+
agent,
|
|
376
|
+
map,
|
|
377
|
+
profileOverride = "auto",
|
|
378
|
+
nested = false,
|
|
379
|
+
geminiProfileResolver = resolveGeminiProfileForPurpose,
|
|
380
|
+
) {
|
|
381
|
+
if (!/^[A-Za-z0-9_.-]+$/.test(agent)) {
|
|
382
|
+
throw new Error(`invalid agent: ${agent}`);
|
|
383
|
+
}
|
|
384
|
+
if (!Object.hasOwn(map, agent)) {
|
|
385
|
+
throw new Error(
|
|
386
|
+
`알 수 없는 에이전트 타입: ${agent}; available: ${Object.keys(map).join(", ")}`,
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
const provider = map[agent];
|
|
390
|
+
if (!["codex", "antigravity", "claude"].includes(provider)) {
|
|
391
|
+
throw new Error(`invalid provider for ${agent}: ${provider}`);
|
|
392
|
+
}
|
|
393
|
+
const policy = resolveDirectCodexRoutePolicy(agent);
|
|
394
|
+
const normalizedOverride = normalizeCodexProfileName(profileOverride);
|
|
395
|
+
let resolvedOverride = policy.profile;
|
|
396
|
+
if (normalizedOverride !== "auto") {
|
|
397
|
+
try {
|
|
398
|
+
resolvedOverride = resolveCodexAgentProfile(agent, {
|
|
399
|
+
profileOverride: normalizedOverride,
|
|
400
|
+
nested,
|
|
401
|
+
});
|
|
402
|
+
} catch {
|
|
403
|
+
throw new Error(
|
|
404
|
+
`TFX_CODEX_PROFILE 값이 유효하지 않습니다: ${profileOverride}`,
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return [
|
|
409
|
+
provider,
|
|
410
|
+
policy.profile,
|
|
411
|
+
policy.timeoutSec,
|
|
412
|
+
policy.runMode,
|
|
413
|
+
policy.opusOversight,
|
|
414
|
+
policy.mcpHint,
|
|
415
|
+
policy.subcommand,
|
|
416
|
+
normalizedOverride,
|
|
417
|
+
resolvedOverride,
|
|
418
|
+
geminiProfileResolver(agent),
|
|
419
|
+
MIN_TIMEOUT_BY_AGENT[agent] ?? 120,
|
|
420
|
+
EXPECTED_DURATION_BY_AGENT[agent] ?? 30,
|
|
421
|
+
NATIVE_CODEX_AGENTS.has(agent),
|
|
422
|
+
];
|
|
324
423
|
}
|
|
325
424
|
|
|
326
425
|
function parseCliArgs(argv) {
|
|
327
426
|
const args = [...argv];
|
|
328
427
|
let format = "json";
|
|
329
428
|
let agent = DEFAULT_CODEX_AGENT;
|
|
429
|
+
let mapFile = "";
|
|
430
|
+
let profileOverride = "auto";
|
|
431
|
+
let geminiProfilesModule = "";
|
|
432
|
+
let nested = false;
|
|
330
433
|
while (args.length > 0) {
|
|
331
434
|
const flag = args.shift();
|
|
332
435
|
if (flag === "--format") format = args.shift() || "";
|
|
333
436
|
else if (flag === "--agent") agent = args.shift() || "";
|
|
437
|
+
else if (flag === "--map-file") mapFile = args.shift() || "";
|
|
438
|
+
else if (flag === "--profile-override")
|
|
439
|
+
profileOverride = args.shift() || "";
|
|
440
|
+
else if (flag === "--gemini-profiles-module")
|
|
441
|
+
geminiProfilesModule = args.shift() || "";
|
|
442
|
+
else if (flag === "--nested") nested = true;
|
|
334
443
|
else throw new Error(`unknown argument: ${flag}`);
|
|
335
444
|
}
|
|
336
|
-
if (!new Set(["json", "tsv"]).has(format)) {
|
|
445
|
+
if (!new Set(["json", "tsv", "route"]).has(format)) {
|
|
337
446
|
throw new Error(`unsupported format: ${format}`);
|
|
338
447
|
}
|
|
339
|
-
return {
|
|
448
|
+
return {
|
|
449
|
+
format,
|
|
450
|
+
agent,
|
|
451
|
+
mapFile,
|
|
452
|
+
profileOverride,
|
|
453
|
+
geminiProfilesModule,
|
|
454
|
+
nested,
|
|
455
|
+
};
|
|
340
456
|
}
|
|
341
457
|
|
|
342
|
-
function runCli() {
|
|
343
|
-
const {
|
|
458
|
+
async function runCli() {
|
|
459
|
+
const {
|
|
460
|
+
format,
|
|
461
|
+
agent,
|
|
462
|
+
mapFile,
|
|
463
|
+
profileOverride,
|
|
464
|
+
geminiProfilesModule,
|
|
465
|
+
nested,
|
|
466
|
+
} = parseCliArgs(process.argv.slice(2));
|
|
467
|
+
if (format === "route") {
|
|
468
|
+
if (!mapFile) throw new Error("--map-file is required for route format");
|
|
469
|
+
const map = JSON.parse(readFileSync(mapFile, "utf8"));
|
|
470
|
+
let resolver = resolveGeminiProfileForPurpose;
|
|
471
|
+
if (geminiProfilesModule) {
|
|
472
|
+
try {
|
|
473
|
+
const module = await import(pathToFileURL(geminiProfilesModule).href);
|
|
474
|
+
resolver = (agent) => {
|
|
475
|
+
try {
|
|
476
|
+
return module.resolveGeminiProfileForPurpose(agent) || "flash38";
|
|
477
|
+
} catch {
|
|
478
|
+
return "flash38";
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
} catch {
|
|
482
|
+
resolver = () => "flash38";
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
process.stdout.write(
|
|
486
|
+
resolveAgentRoute(agent, map, profileOverride, nested, resolver).join(
|
|
487
|
+
"\x1e",
|
|
488
|
+
),
|
|
489
|
+
);
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
344
492
|
const resolved = resolveCodexAgentPolicy(agent);
|
|
345
493
|
if (format === "tsv") {
|
|
346
494
|
process.stdout.write(
|
|
@@ -360,7 +508,7 @@ function runCli() {
|
|
|
360
508
|
|
|
361
509
|
if (import.meta.url === new URL(process.argv[1], "file:").href) {
|
|
362
510
|
try {
|
|
363
|
-
runCli();
|
|
511
|
+
await runCli();
|
|
364
512
|
} catch (error) {
|
|
365
513
|
process.stderr.write(`[agent-route-policy] ${error.message}\n`);
|
|
366
514
|
process.exitCode = 1;
|
|
@@ -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 "@triflux/core/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 };
|