triflux 10.45.0 → 10.46.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/bin/tfx-live.mjs +576 -53
- package/config/routing-policy.json +1 -1
- package/hub/dynamic-routing-engine.mjs +1 -1
- package/hub/team/retry-state-machine.mjs +9 -1
- package/hub/team/uds-orchestrator.mjs +384 -83
- package/hub/workers/codex-mcp.mjs +1 -1
- package/package.json +1 -1
- package/scripts/__tests__/release-governance.test.mjs +235 -11
- package/scripts/__tests__/release-publish-ancestor.test.mjs +67 -0
- package/scripts/__tests__/release-verify-wait.test.mjs +169 -0
- package/scripts/__tests__/resolve-auto-release.test.mjs +216 -0
- package/scripts/__tests__/tfx-route-bash-node-parity.test.mjs +1 -1
- package/scripts/__tests__/tfx-route-node-entry.test.mjs +4 -4
- package/scripts/lib/agent-route-policy.mjs +18 -17
- package/scripts/lib/codex-profile-config.mjs +19 -5
- package/scripts/release/publish.mjs +16 -1
- package/scripts/release/resolve-auto-release.mjs +136 -0
- package/scripts/release/verify.mjs +74 -25
- package/scripts/setup.mjs +13 -7
- package/scripts/tfx-route.sh +26 -23
- package/skills/tfx-auto/SKILL.md +5 -4
- package/skills/tfx-live/SKILL.md +112 -7
- package/skills/tfx-profile/SKILL.md +9 -7
- package/skills/tfx-setup/SKILL.md +1 -1
- package/skills/tfx-ship/SKILL.md +43 -36
- package/tui/codex-profile.mjs +2 -3
- package/tui/setup.mjs +4 -3
|
@@ -4,6 +4,11 @@ import { fileURLToPath } from "node:url";
|
|
|
4
4
|
import { assertVersionSync, parseArgs, ROOT } from "./lib.mjs";
|
|
5
5
|
|
|
6
6
|
const NPM_VERIFY_TARGETS = ["@triflux/core", "@triflux/remote", "triflux"];
|
|
7
|
+
const NPM_POLL_INTERVAL_MS = 20_000;
|
|
8
|
+
|
|
9
|
+
function sleep(ms) {
|
|
10
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
11
|
+
}
|
|
7
12
|
|
|
8
13
|
function errorMessage(error) {
|
|
9
14
|
const stderr = error?.stderr?.toString?.().trim();
|
|
@@ -16,7 +21,20 @@ export async function verifyRelease({
|
|
|
16
21
|
rootDir = ROOT,
|
|
17
22
|
dryRun = true,
|
|
18
23
|
execFileSyncFn = execFileSync,
|
|
24
|
+
npmWaitSeconds = 0,
|
|
25
|
+
npmPendingOk = false,
|
|
26
|
+
nowFn = Date.now,
|
|
27
|
+
sleepFn = sleep,
|
|
19
28
|
} = {}) {
|
|
29
|
+
const waitSeconds = Number(npmWaitSeconds);
|
|
30
|
+
if (
|
|
31
|
+
typeof npmWaitSeconds === "boolean" ||
|
|
32
|
+
npmWaitSeconds === "" ||
|
|
33
|
+
!Number.isFinite(waitSeconds) ||
|
|
34
|
+
waitSeconds < 0
|
|
35
|
+
) {
|
|
36
|
+
throw new Error("--npm-wait-seconds must be a non-negative finite number");
|
|
37
|
+
}
|
|
20
38
|
const sync = assertVersionSync({ rootDir });
|
|
21
39
|
if (!sync.ok) {
|
|
22
40
|
throw new Error("Version sync failed. Fix metadata before verify.");
|
|
@@ -31,32 +49,61 @@ export async function verifyRelease({
|
|
|
31
49
|
];
|
|
32
50
|
|
|
33
51
|
if (!dryRun) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
52
|
+
const deadline = nowFn() + waitSeconds * 1000;
|
|
53
|
+
const lastSeen = new Map();
|
|
54
|
+
while (true) {
|
|
55
|
+
const npmChecks = NPM_VERIFY_TARGETS.map((packageName) => {
|
|
56
|
+
const packageSpec = `${packageName}@${releaseVersion}`;
|
|
57
|
+
try {
|
|
58
|
+
const npmVersion = execFileSyncFn(
|
|
59
|
+
"npm",
|
|
60
|
+
["view", packageSpec, "version"],
|
|
61
|
+
{ cwd: rootDir, encoding: "utf8" },
|
|
62
|
+
).trim();
|
|
63
|
+
lastSeen.set(packageName, npmVersion);
|
|
64
|
+
return {
|
|
65
|
+
name: `npm-view ${packageName}`,
|
|
66
|
+
ok: npmVersion === releaseVersion,
|
|
67
|
+
detail:
|
|
68
|
+
npmVersion === releaseVersion
|
|
69
|
+
? npmVersion
|
|
70
|
+
: `expected ${releaseVersion}, got ${npmVersion}`,
|
|
71
|
+
};
|
|
72
|
+
} catch (error) {
|
|
73
|
+
const previous = lastSeen.get(packageName);
|
|
74
|
+
const lastSeenDetail =
|
|
75
|
+
waitSeconds > 0 ? ` (last seen ${previous ?? "unavailable"})` : "";
|
|
76
|
+
return {
|
|
77
|
+
name: `npm-view ${packageName}`,
|
|
78
|
+
ok: false,
|
|
79
|
+
detail: `npm view failed for ${packageSpec}: ${errorMessage(error)}${lastSeenDetail}`,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
const remainingMs = deadline - nowFn();
|
|
84
|
+
if (npmChecks.every((check) => check.ok) || remainingMs <= 0) {
|
|
85
|
+
if (npmPendingOk && remainingMs <= 0) {
|
|
86
|
+
const pending = npmChecks.filter((check) => !check.ok);
|
|
87
|
+
for (const check of pending) {
|
|
88
|
+
const packageName = check.name.slice("npm-view ".length);
|
|
89
|
+
check.ok = null;
|
|
90
|
+
check.status = "pending";
|
|
91
|
+
check.detail = `expected ${releaseVersion}, last seen ${lastSeen.get(packageName) ?? "unavailable"}; npm publish-time scan may still be processing`;
|
|
92
|
+
}
|
|
93
|
+
if (pending.length && process.env.GITHUB_ACTIONS === "true") {
|
|
94
|
+
const packages = pending.map(
|
|
95
|
+
(check) =>
|
|
96
|
+
`${check.name.slice("npm-view ".length)}@${releaseVersion}`,
|
|
97
|
+
);
|
|
98
|
+
console.log(
|
|
99
|
+
`::warning::npm registry pending for ${packages.join(", ")}; npm publish-time scan may still be processing`,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
checks.push(...npmChecks);
|
|
104
|
+
break;
|
|
59
105
|
}
|
|
106
|
+
await sleepFn(Math.min(NPM_POLL_INTERVAL_MS, remainingMs));
|
|
60
107
|
}
|
|
61
108
|
|
|
62
109
|
const ghRelease = execFileSyncFn(
|
|
@@ -101,6 +148,8 @@ if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
|
101
148
|
version: args.version,
|
|
102
149
|
rootDir: args.root,
|
|
103
150
|
dryRun: !args.execute,
|
|
151
|
+
npmWaitSeconds: args["npm-wait-seconds"] ?? 0,
|
|
152
|
+
npmPendingOk: args["npm-pending-ok"] === true,
|
|
104
153
|
});
|
|
105
154
|
console.log(JSON.stringify(result, null, 2));
|
|
106
155
|
process.exitCode = result.ok ? 0 : 1;
|
package/scripts/setup.mjs
CHANGED
|
@@ -437,17 +437,23 @@ const REQUIRED_CODEX_PROFILES = [
|
|
|
437
437
|
name: "gpt6_astra_xhigh",
|
|
438
438
|
lines: ['model = "gpt-6-astra"', 'model_reasoning_effort = "xhigh"'],
|
|
439
439
|
},
|
|
440
|
+
// GPT-6 Sol: implementation, review, and verification role defaults.
|
|
440
441
|
{
|
|
441
|
-
name: "
|
|
442
|
-
lines: ['model = "gpt-
|
|
442
|
+
name: "gpt6_sol_high",
|
|
443
|
+
lines: ['model = "gpt-6-sol"', 'model_reasoning_effort = "high"'],
|
|
443
444
|
},
|
|
444
445
|
{
|
|
445
|
-
name: "
|
|
446
|
-
lines: ['model = "gpt-
|
|
446
|
+
name: "gpt6_sol_med",
|
|
447
|
+
lines: ['model = "gpt-6-sol"', 'model_reasoning_effort = "medium"'],
|
|
447
448
|
},
|
|
449
|
+
// GPT-6 Luna: high for focused lanes, low for latency-first lanes.
|
|
448
450
|
{
|
|
449
|
-
name: "
|
|
450
|
-
lines: ['model = "gpt-
|
|
451
|
+
name: "gpt6_luna_high",
|
|
452
|
+
lines: ['model = "gpt-6-luna"', 'model_reasoning_effort = "high"'],
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
name: "gpt6_luna_low",
|
|
456
|
+
lines: ['model = "gpt-6-luna"', 'model_reasoning_effort = "low"'],
|
|
451
457
|
},
|
|
452
458
|
];
|
|
453
459
|
|
|
@@ -1391,7 +1397,7 @@ function ensureCodexProfiles() {
|
|
|
1391
1397
|
}
|
|
1392
1398
|
}
|
|
1393
1399
|
// config.toml 의 모든 inline [profiles.*] 마이그레이션 (Codex 0.134 는 inline 거부).
|
|
1394
|
-
// - 관리
|
|
1400
|
+
// - 관리 gpt6_*: 위에서 canonical 별도 파일을 썼으므로 inline 만 제거.
|
|
1395
1401
|
// - 구형 retired (codex53/spark53/gpt54/mini54): inline 제거, 재생성 안 함.
|
|
1396
1402
|
// - 그 외 사용자 커스텀: 본문을 보존해 별도 파일로 이관한 뒤 inline 제거.
|
|
1397
1403
|
// (별도 파일이 이미 있으면 덮어쓰지 않는다 — 사용자 수정 보존.)
|
package/scripts/tfx-route.sh
CHANGED
|
@@ -1424,7 +1424,7 @@ route_agent() {
|
|
|
1424
1424
|
exit 1
|
|
1425
1425
|
}
|
|
1426
1426
|
IFS=$'\t' read -r policy_profile policy_timeout policy_mode policy_oversight policy_mcp policy_subcommand <<< "$policy_row"
|
|
1427
|
-
case "$policy_profile" in gpt6_astra_xhigh|
|
|
1427
|
+
case "$policy_profile" in gpt6_astra_xhigh|gpt6_sol_high|gpt6_sol_med|gpt6_luna_high|gpt6_luna_low) ;; *) echo "ERROR: invalid Codex profile policy" >&2; exit 1 ;; esac
|
|
1428
1428
|
[[ "$policy_timeout" =~ ^[1-9][0-9]*$ ]] || { echo "ERROR: invalid Codex timeout policy" >&2; exit 1; }
|
|
1429
1429
|
case "$policy_mode" in fg|bg) ;; *) echo "ERROR: invalid Codex run mode policy" >&2; exit 1 ;; esac
|
|
1430
1430
|
case "$policy_oversight" in true|false) ;; *) echo "ERROR: invalid Codex oversight policy" >&2; exit 1 ;; esac
|
|
@@ -1495,6 +1495,9 @@ normalize_codex_profile_name() {
|
|
|
1495
1495
|
gpt56_sol_xhigh) echo "gpt6_astra_xhigh" ;;
|
|
1496
1496
|
gpt56_sol_max) echo "gpt6_astra_max" ;;
|
|
1497
1497
|
gpt56_sol_ultra) echo "gpt6_astra_ultra" ;;
|
|
1498
|
+
gpt56_terra_high) echo "gpt6_sol_high" ;;
|
|
1499
|
+
gpt56_terra_med) echo "gpt6_sol_med" ;;
|
|
1500
|
+
gpt56_luna_low) echo "gpt6_luna_low" ;;
|
|
1498
1501
|
*) echo "$1" ;;
|
|
1499
1502
|
esac
|
|
1500
1503
|
}
|
|
@@ -1585,9 +1588,9 @@ case "$TFX_CODEX_TRANSPORT" in
|
|
|
1585
1588
|
;;
|
|
1586
1589
|
esac
|
|
1587
1590
|
case "$TFX_CODEX_PROFILE" in
|
|
1588
|
-
auto|max|ultra|
|
|
1591
|
+
auto|max|ultra|gpt6_luna_low|gpt6_luna_high|gpt6_sol_med|gpt6_sol_high|gpt6_astra_xhigh|gpt6_astra_max|gpt6_astra_ultra) ;;
|
|
1589
1592
|
*)
|
|
1590
|
-
echo "ERROR: TFX_CODEX_PROFILE 값은 auto, max, ultra 또는 canonical
|
|
1593
|
+
echo "ERROR: TFX_CODEX_PROFILE 값은 auto, max, ultra 또는 canonical gpt6_* profile이어야 합니다. (현재: $TFX_CODEX_PROFILE)" >&2
|
|
1591
1594
|
exit 1
|
|
1592
1595
|
;;
|
|
1593
1596
|
esac
|
|
@@ -1640,9 +1643,9 @@ apply_cli_mode() {
|
|
|
1640
1643
|
designer|antigravity|agy|gemini)
|
|
1641
1644
|
CLI_ARGS="exec --profile gpt6_astra_xhigh ${codex_base}"; CLI_EFFORT="gpt6_astra_xhigh"; DEFAULT_TIMEOUT=3600 ;;
|
|
1642
1645
|
writer)
|
|
1643
|
-
CLI_ARGS="exec --profile
|
|
1646
|
+
CLI_ARGS="exec --profile gpt6_luna_high ${codex_base}"; CLI_EFFORT="gpt6_luna_high"; DEFAULT_TIMEOUT=900 ;;
|
|
1644
1647
|
*)
|
|
1645
|
-
CLI_ARGS="exec --profile
|
|
1648
|
+
CLI_ARGS="exec --profile gpt6_sol_high ${codex_base}"; CLI_EFFORT="gpt6_sol_high"; DEFAULT_TIMEOUT=1080 ;;
|
|
1646
1649
|
esac
|
|
1647
1650
|
echo "[tfx-route] TFX_CLI_MODE=codex: $AGENT_TYPE → codex($CLI_EFFORT)로 리매핑" >&2
|
|
1648
1651
|
fi ;;
|
|
@@ -1788,10 +1791,10 @@ apply_plan_guard() {
|
|
|
1788
1791
|
if [[ "$replacement" == "$CLI_EFFORT" ]]; then
|
|
1789
1792
|
replacement=""
|
|
1790
1793
|
case "$CLI_EFFORT" in
|
|
1791
|
-
gpt55_low|spark53_low|codex53_low|gpt54_low|mini54_low) replacement="
|
|
1792
|
-
gpt55_med|spark53_med|codex53_med|mini54_med) replacement="
|
|
1794
|
+
gpt55_low|spark53_low|codex53_low|gpt54_low|mini54_low) replacement="gpt6_luna_low" ;;
|
|
1795
|
+
gpt55_med|spark53_med|codex53_med|mini54_med) replacement="gpt6_sol_med" ;;
|
|
1793
1796
|
gpt55_xhigh|codex53_xhigh|gpt54_xhigh) replacement="gpt6_astra_xhigh" ;;
|
|
1794
|
-
gpt55_high|spark53_*|codex53_*|gpt54_*|mini54_*) replacement="
|
|
1797
|
+
gpt55_high|spark53_*|codex53_*|gpt54_*|mini54_*) replacement="gpt6_sol_high" ;;
|
|
1795
1798
|
esac
|
|
1796
1799
|
fi
|
|
1797
1800
|
[[ -z "$replacement" ]] && return
|
|
@@ -1822,29 +1825,29 @@ apply_no_claude_native_mode() {
|
|
|
1822
1825
|
|
|
1823
1826
|
case "$AGENT_TYPE" in
|
|
1824
1827
|
explore)
|
|
1825
|
-
CLI_ARGS="exec --profile
|
|
1826
|
-
CLI_EFFORT="
|
|
1828
|
+
CLI_ARGS="exec --profile gpt6_luna_low ${codex_base}"
|
|
1829
|
+
CLI_EFFORT="gpt6_luna_low"
|
|
1827
1830
|
DEFAULT_TIMEOUT=600
|
|
1828
1831
|
RUN_MODE="fg"
|
|
1829
1832
|
OPUS_OVERSIGHT="false"
|
|
1830
1833
|
;;
|
|
1831
1834
|
verifier)
|
|
1832
|
-
CLI_ARGS="exec --profile
|
|
1833
|
-
CLI_EFFORT="
|
|
1835
|
+
CLI_ARGS="exec --profile gpt6_sol_high ${codex_base} review"
|
|
1836
|
+
CLI_EFFORT="gpt6_sol_high"
|
|
1834
1837
|
DEFAULT_TIMEOUT=1200
|
|
1835
1838
|
RUN_MODE="fg"
|
|
1836
1839
|
OPUS_OVERSIGHT="false"
|
|
1837
1840
|
;;
|
|
1838
1841
|
test-engineer)
|
|
1839
|
-
CLI_ARGS="exec --profile
|
|
1840
|
-
CLI_EFFORT="
|
|
1842
|
+
CLI_ARGS="exec --profile gpt6_sol_high ${codex_base}"
|
|
1843
|
+
CLI_EFFORT="gpt6_sol_high"
|
|
1841
1844
|
DEFAULT_TIMEOUT=1200
|
|
1842
1845
|
RUN_MODE="bg"
|
|
1843
1846
|
OPUS_OVERSIGHT="false"
|
|
1844
1847
|
;;
|
|
1845
1848
|
qa-tester)
|
|
1846
|
-
CLI_ARGS="exec --profile
|
|
1847
|
-
CLI_EFFORT="
|
|
1849
|
+
CLI_ARGS="exec --profile gpt6_sol_high ${codex_base} review"
|
|
1850
|
+
CLI_EFFORT="gpt6_sol_high"
|
|
1848
1851
|
DEFAULT_TIMEOUT=1200
|
|
1849
1852
|
RUN_MODE="bg"
|
|
1850
1853
|
OPUS_OVERSIGHT="false"
|
|
@@ -1876,7 +1879,7 @@ resolve_safe_retry_codex_profile() {
|
|
|
1876
1879
|
echo "gpt6_astra_max"
|
|
1877
1880
|
return
|
|
1878
1881
|
;;
|
|
1879
|
-
|
|
1882
|
+
gpt6_luna_low|gpt6_luna_high|gpt6_sol_med|gpt6_sol_high|gpt6_astra_xhigh|gpt6_astra_max)
|
|
1880
1883
|
echo "$profile"
|
|
1881
1884
|
return
|
|
1882
1885
|
;;
|
|
@@ -1886,7 +1889,7 @@ resolve_safe_retry_codex_profile() {
|
|
|
1886
1889
|
codex_home="${CODEX_HOME:-${TFX_CODEX_HOME:-${HOME:-}/.codex}}"
|
|
1887
1890
|
profile_file="${codex_home}/${profile}.config.toml"
|
|
1888
1891
|
if [[ ! -f "$profile_file" ]]; then
|
|
1889
|
-
echo "
|
|
1892
|
+
echo "gpt6_sol_high"
|
|
1890
1893
|
return
|
|
1891
1894
|
fi
|
|
1892
1895
|
effort=$(awk -F= '
|
|
@@ -1900,7 +1903,7 @@ resolve_safe_retry_codex_profile() {
|
|
|
1900
1903
|
' "$profile_file" 2>/dev/null || true)
|
|
1901
1904
|
case "$effort" in
|
|
1902
1905
|
ultra) echo "gpt6_astra_max" ;;
|
|
1903
|
-
"") echo "
|
|
1906
|
+
"") echo "gpt6_sol_high" ;;
|
|
1904
1907
|
*) echo "$profile" ;;
|
|
1905
1908
|
esac
|
|
1906
1909
|
}
|
|
@@ -2113,8 +2116,8 @@ apply_dynamic_routing_override() {
|
|
|
2113
2116
|
local codex_base
|
|
2114
2117
|
codex_base="$(build_codex_base)"
|
|
2115
2118
|
CLI_CMD="codex"
|
|
2116
|
-
CLI_ARGS="exec --profile
|
|
2117
|
-
CLI_EFFORT="
|
|
2119
|
+
CLI_ARGS="exec --profile gpt6_sol_high ${codex_base}"
|
|
2120
|
+
CLI_EFFORT="gpt6_sol_high"; DEFAULT_TIMEOUT=1080; RUN_MODE="fg"; OPUS_OVERSIGHT="false"
|
|
2118
2121
|
;;
|
|
2119
2122
|
antigravity)
|
|
2120
2123
|
CLI_CMD="agy"
|
|
@@ -3071,12 +3074,12 @@ run_codex_exec() {
|
|
|
3071
3074
|
_attempt_codex_run
|
|
3072
3075
|
|
|
3073
3076
|
# Tier-fallback: ChatGPT account가 높은 reasoning profile을 거부하면
|
|
3074
|
-
# GPT-
|
|
3077
|
+
# GPT-6 Luna low로만 1회 재시도한다. 구형 gpt-5.4 계열로는 더 이상 다운그레이드하지 않는다.
|
|
3075
3078
|
# 출처: Issue #211 — codex CLI tier-aware fallback.
|
|
3076
3079
|
if [[ "$exit_code_local" -ne 0 ]] && grep -qE "is not supported when using Codex with a ChatGPT account" "$STDERR_LOG" 2>/dev/null; then
|
|
3077
3080
|
local fallback_profile=""
|
|
3078
3081
|
case "$CLI_EFFORT" in
|
|
3079
|
-
gpt6_astra_ultra|gpt6_astra_max|gpt6_astra_xhigh|
|
|
3082
|
+
gpt6_astra_ultra|gpt6_astra_max|gpt6_astra_xhigh|gpt6_sol_high|gpt6_sol_med|gpt6_luna_high) fallback_profile="gpt6_luna_low" ;;
|
|
3080
3083
|
esac
|
|
3081
3084
|
if [[ -n "$fallback_profile" ]]; then
|
|
3082
3085
|
echo "[tfx-route] tier fallback: $CLI_EFFORT not supported on ChatGPT account → retry with $fallback_profile" >&2
|
package/skills/tfx-auto/SKILL.md
CHANGED
|
@@ -1009,10 +1009,11 @@ Agent(subagent_type="oh-my-claudecode:{agent}", model="{model}", prompt="{prompt
|
|
|
1009
1009
|
|
|
1010
1010
|
| 입력 | CLI | MCP |
|
|
1011
1011
|
|------|-----|-----|
|
|
1012
|
-
| codex / executor
|
|
1013
|
-
| debugger / deep-executor | Codex (
|
|
1014
|
-
| build-fixer | Codex (
|
|
1015
|
-
|
|
|
1012
|
+
| codex / executor | Codex (`gpt6_sol_high`; 복잡 구현은 deep-executor/xhigh) | implement |
|
|
1013
|
+
| debugger / deep-executor | Codex (`gpt6_astra_xhigh`) | implement |
|
|
1014
|
+
| build-fixer | Codex (`gpt6_luna_high`) | implement |
|
|
1015
|
+
| spark | Codex (`gpt6_luna_low`) | implement |
|
|
1016
|
+
| architect / planner / critic / analyst | Codex (`gpt6_astra_xhigh`) | analyze |
|
|
1016
1017
|
| scientist / document-specialist | Codex | analyze |
|
|
1017
1018
|
| code-reviewer / security-reviewer / quality-reviewer | Codex (review) | review |
|
|
1018
1019
|
| antigravity / designer / writer | Antigravity | docs |
|
package/skills/tfx-live/SKILL.md
CHANGED
|
@@ -3,19 +3,20 @@ name: tfx-live
|
|
|
3
3
|
description: >
|
|
4
4
|
Use when Claude, Codex, or a Triflux worker needs live Claude↔Codex orchestration:
|
|
5
5
|
start/ask/stop, multi-turn, peer relay, daemon UDS attach, or UDS-first with tmux fallback.
|
|
6
|
-
argument-hint: "<start|ask|stop|probe|list-sessions|peer|converse|goal-driven> ..."
|
|
6
|
+
argument-hint: "<start|ask|stop|interrupt|probe|list-sessions|peer|converse|goal-driven|orchestrate> ..."
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
# tfx-live
|
|
9
|
+
# tfx-live: Claude↔Codex live orchestration
|
|
10
10
|
|
|
11
11
|
`tfx-live` is the Triflux-owned live bridge for Claude Code and Codex TUI sessions.
|
|
12
|
-
It runs as a local CLI (`tfx-live`) and supports tmux TUI control plus Claude daemon UDS attach.
|
|
12
|
+
It runs as a local CLI (`tfx-live`) and supports tmux TUI control plus Claude and Codex daemon UDS attach.
|
|
13
13
|
|
|
14
14
|
## Default transport
|
|
15
15
|
|
|
16
16
|
- Claude targets with `--short` or `--session-id`: **UDS-first `auto` by default**.
|
|
17
17
|
- If UDS probe/attach fails: write `~/.claude/cache/triflux/tfx-live/bug-reports/uds-fallback-*.json`, then tmux fallback when `--session` is provided.
|
|
18
18
|
- Codex targets and Claude targets without a daemon ref: tmux by default.
|
|
19
|
+
- Codex existing threads use explicit `--transport uds --thread <id|auto>`; `auto` transport remains Claude-only.
|
|
19
20
|
- UDS completion contract: `done=true` only when `matchedCompletion===true`; `timedOut` or `closed` is not completion.
|
|
20
21
|
- Bridge resolution: `--bridge` > `$TFX_BRIDGE` > `$TFX_REPO_ROOT/hub/bridge.mjs` > bundled Triflux `hub/bridge.mjs`.
|
|
21
22
|
|
|
@@ -58,7 +59,8 @@ tfx-live ask --cli claude --transport uds --short <8hex> --prompt "..." --timeou
|
|
|
58
59
|
|
|
59
60
|
List the Codex CLI sessions already running in tmux; this does not require a
|
|
60
61
|
prior `tfx-live start`. The JSON result includes the tmux session name, start
|
|
61
|
-
time, current working directory,
|
|
62
|
+
time, current working directory, attachment state, `panes: [{ target, cwd,
|
|
63
|
+
command }]` for Codex panes, and `target` for the first matching pane.
|
|
62
64
|
|
|
63
65
|
```bash
|
|
64
66
|
# All locally running Codex tmux sessions
|
|
@@ -70,7 +72,73 @@ tfx-live list-sessions --cli codex --cwd ~/Projects/my-worktree
|
|
|
70
72
|
|
|
71
73
|
`probe` remains the Claude daemon/UDS discovery command. Use
|
|
72
74
|
`list-sessions --cli codex` when selecting an existing Codex tmux session to
|
|
73
|
-
pass to `tfx-live ask --session <name>`.
|
|
75
|
+
pass to `tfx-live ask --session <name:window.pane>`.
|
|
76
|
+
Discovery and peer preflight also inspect pane tty/descendant processes for
|
|
77
|
+
the npm `node .../codex` shim, `codex.js`, and native `codex` executables.
|
|
78
|
+
`codex-code-mode-host` does not count. With `--remote`, the process check runs
|
|
79
|
+
on that host; unavailable `ps` produces a peer `preflightWarning` and allows
|
|
80
|
+
attach to continue.
|
|
81
|
+
|
|
82
|
+
`ask` and tmux `interrupt` accept session names, `name:window`, and
|
|
83
|
+
`name:window.pane`. `start` and `stop` require a session name only; `stop`
|
|
84
|
+
rejects pane targets because it kills the entire session. `--remote HOST`
|
|
85
|
+
preserves the complete target.
|
|
86
|
+
`stop` also rejects pane/window IDs such as `%12` and `@3`, and dotted targets.
|
|
87
|
+
|
|
88
|
+
Before pasting, `ask` checks the visible pane for active work and Codex
|
|
89
|
+
`model: loading`. `--if-busy wait|fail|interrupt` defaults to `wait`.
|
|
90
|
+
`--busy-timeout SECONDS` defaults to `--timeout`; `--poll-interval` is in
|
|
91
|
+
milliseconds. A busy timeout sends no prompt. `interrupt` sends Escape,
|
|
92
|
+
then waits for idle before pasting. Results include `ifBusy` and
|
|
93
|
+
`busyWaitedMs`. `converse`, `goal-driven`, and `peer` accept these flags too.
|
|
94
|
+
Only the last visible `model:` header controls loading detection. Time-only
|
|
95
|
+
lines are removed from the response tail; times inside the answer remain.
|
|
96
|
+
|
|
97
|
+
## Existing Codex TUI thread over UDS
|
|
98
|
+
|
|
99
|
+
With `daemon_auto_start` enabled, Codex TUI runs on a shared local app-server
|
|
100
|
+
daemon. Its default socket is
|
|
101
|
+
`$CODEX_HOME/app-server-control/app-server-control.sock`, or the same path
|
|
102
|
+
under `~/.codex` when `CODEX_HOME` is unset. `--codex-socket PATH|default`
|
|
103
|
+
selects an existing socket and follows symlinks. If absent, start the daemon
|
|
104
|
+
with `codex app-server daemon start`.
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
tfx-live list-sessions --cli codex --transport uds --cwd ~/Projects/my-worktree
|
|
108
|
+
tfx-live ask --cli codex --transport uds --thread auto \
|
|
109
|
+
--cwd ~/Projects/my-worktree --prompt "현재 변경사항을 요약해줘" \
|
|
110
|
+
--if-busy wait --busy-timeout 120 --timeout 120
|
|
111
|
+
tfx-live ask --cli codex --transport uds --thread <id> \
|
|
112
|
+
--codex-socket default --prompt "추가 확인 사항" --if-busy fail
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
No `--session`, `--short`, or `--session-id` is required. Discovery returns
|
|
116
|
+
`[{ threadId, cwd, name, status, source }]` for loaded threads. `--thread auto`
|
|
117
|
+
selects exactly one loaded thread after optional realpath-normalized `--cwd`
|
|
118
|
+
filtering. Start the TUI and send its first message if none is loaded. Multiple
|
|
119
|
+
matches produce an error listing candidates; select an explicit thread ID.
|
|
120
|
+
|
|
121
|
+
The client calls `thread/resume` with `excludeTurns: true` before `turn/start`
|
|
122
|
+
to subscribe to response notifications. It does not request historical turns.
|
|
123
|
+
UDS `--if-busy wait|fail|steer` defaults to `wait`.
|
|
124
|
+
`steered` uses the returned turn ID and fresh `turn/started` events after
|
|
125
|
+
subscription, not the earlier busy snapshot; when a current turn ID is
|
|
126
|
+
available, `turn/steer` sends it as `expectedTurnId`.
|
|
127
|
+
The fallback treats no fresh matching `turn/started` before completion as a
|
|
128
|
+
reused turn; it relies on ordered notifications from the resumed connection.
|
|
129
|
+
Completion matches the returned turn ID. Closing the client
|
|
130
|
+
does not archive or delete the thread, or start or stop the shared daemon.
|
|
131
|
+
Responses prefer completed `final_answer` items, joined with blank lines;
|
|
132
|
+
without one, the last agent message is used. Intermediate commentary is
|
|
133
|
+
returned separately. Retryable error notifications keep the turn open and
|
|
134
|
+
increment `meta.retries`.
|
|
135
|
+
`--max-turn SECONDS` sets the Codex UDS activity extension ceiling. Remote
|
|
136
|
+
relay timeouts include busy waiting, the full turn allowance, and a relay margin.
|
|
137
|
+
Codex relay budgets also allow the next lifecycle timer check after that ceiling.
|
|
138
|
+
|
|
139
|
+
Codex UDS `interrupt` is unavailable: `turn/interrupt` requires a turn ID,
|
|
140
|
+
which the supported metadata-only `thread/read` does not provide. Use tmux
|
|
141
|
+
`interrupt --session <target>` when the TUI pane is available.
|
|
74
142
|
|
|
75
143
|
## Peer relay
|
|
76
144
|
|
|
@@ -86,9 +154,41 @@ tfx-live peer --cli-a codex --cli-b claude \
|
|
|
86
154
|
--session-a cx-peer --session-b cl-fallback --short-b <8hex> \
|
|
87
155
|
--cwd ~/Projects --mode freeform --seed "서로 응답을 다음 프롬프트로 이어받아라" \
|
|
88
156
|
--rounds 2 --timeout 180
|
|
157
|
+
|
|
158
|
+
# Attach both existing tmux panes without starting or stopping their sessions
|
|
159
|
+
tfx-live peer --cli-a codex --cli-b claude \
|
|
160
|
+
--session-a work:0.1 --session-b work:0.2 --attach-a --attach-b \
|
|
161
|
+
--mode freeform --seed "변경을 검토해줘" --if-busy wait
|
|
162
|
+
|
|
163
|
+
# Existing Codex daemon thread + existing Claude daemon session
|
|
164
|
+
tfx-live peer --cli-a codex --transport-a uds --thread-a <id|auto> \
|
|
165
|
+
--codex-socket-a default --cli-b claude --transport-b uds --short-b <8hex> \
|
|
166
|
+
--cwd ~/Projects/my-worktree --mode freeform --seed "변경을 검토해줘"
|
|
89
167
|
```
|
|
90
168
|
|
|
91
|
-
In `peer`, every hop sends the previous response as the next prompt to the opposite agent; the final JSON
|
|
169
|
+
In `peer`, every hop sends the previous response as the next prompt to the opposite agent; the final JSON includes the transcript and lifecycle metadata.
|
|
170
|
+
|
|
171
|
+
`--attach-a` and `--attach-b` preflight existing tmux targets before any hop.
|
|
172
|
+
Codex targets must identify a Codex pane. Attached sessions are preserved on
|
|
173
|
+
success, error, SIGINT, and SIGTERM. UDS sides are always treated as attached.
|
|
174
|
+
The result includes `attachedA` and `attachedB`. Side-specific UDS flags are
|
|
175
|
+
`--thread-a`, `--thread-b`, `--codex-socket-a`, and `--codex-socket-b`.
|
|
176
|
+
`--if-busy-a` and `--if-busy-b` accept `wait|fail|interrupt` for tmux and
|
|
177
|
+
`wait|fail|steer` for Codex UDS. A common `--if-busy` must be valid for both
|
|
178
|
+
sides even when one side overrides it. UDS done-marker lines are removed
|
|
179
|
+
before relaying a response. `stoppedA`/`stoppedB` are true only after an owned
|
|
180
|
+
session was actually stopped; attached sides stay false.
|
|
181
|
+
|
|
182
|
+
## Known limits
|
|
183
|
+
|
|
184
|
+
- A draft in the TUI composer can merge with the pasted prompt. Changing
|
|
185
|
+
placeholder text prevents reliable detection without terminal styling.
|
|
186
|
+
- `--thread auto` can be ambiguous when an ephemeral orchestrate thread
|
|
187
|
+
remains loaded. The candidate-list error exposes this; choose a thread ID.
|
|
188
|
+
- A TUI's thread stays loaded in the shared daemon after that TUI exits
|
|
189
|
+
(observed with codex-cli 0.156.1: a `tfx-live stop`ped peer session left its
|
|
190
|
+
thread in `thread/loaded/list`). Stale threads in the same cwd make
|
|
191
|
+
`--thread auto` ambiguous, so pin `--thread ID` for long-lived targets.
|
|
92
192
|
|
|
93
193
|
## Orchestrate (Claude UDS + Codex)
|
|
94
194
|
|
|
@@ -101,12 +201,17 @@ tfx-live orchestrate --task "이 변경의 위험을 한 줄로" --mode peer
|
|
|
101
201
|
# experimental: Codex over a real `codex app-server` WebSocket-over-UDS daemon
|
|
102
202
|
tfx-live orchestrate --task "이 변경의 위험을 한 줄로" \
|
|
103
203
|
--mode codex-led --codex-transport app-server-uds --timeout 120
|
|
204
|
+
|
|
205
|
+
# Attach the shared daemon and create a new ephemeral thread for orchestration
|
|
206
|
+
tfx-live orchestrate --task "이 변경의 위험을 한 줄로" \
|
|
207
|
+
--mode codex-led --codex-socket default --timeout 120
|
|
104
208
|
```
|
|
105
209
|
|
|
106
210
|
- `--mode` = `peer` (default) | `codex-led` | `claude-led`.
|
|
107
211
|
- `--codex-transport` = `exec` (default, `codex exec` stdio) | `app-server-uds` (experimental).
|
|
108
212
|
- `app-server-uds` spawns a private `codex app-server --listen unix://<tmp>`, attaches over WebSocket-over-UDS (RFC 6455, masked client frames), runs `initialize`→`thread/start`→`turn/start`, and resolves on `turn/completed`. Transport proven against codex-cli 0.135.0 (see `experiments/native-bridge-feasibility/codex-app-server-uds-smoke.mjs`).
|
|
109
|
-
-
|
|
213
|
+
- `--codex-socket PATH|default` implies `app-server-uds` and conflicts with explicit `--codex-transport exec`. It attaches without spawning or killing the daemon and still creates a new ephemeral thread.
|
|
214
|
+
- Without `--codex-socket`, `app-server-uds` uses the private server path; the default transport remains `exec`.
|
|
110
215
|
- Requires a live Claude daemon (same as the UDS `ask` path) for the Claude side.
|
|
111
216
|
|
|
112
217
|
## Useful diagnostics
|
|
@@ -45,7 +45,7 @@ options:
|
|
|
45
45
|
```
|
|
46
46
|
| 프로파일 | 모델 | Effort |
|
|
47
47
|
|----------|------|--------|
|
|
48
|
-
|
|
|
48
|
+
| gpt6_sol_high | 프로필 설정의 현재 모델 | high |
|
|
49
49
|
| ... | ... | ... |
|
|
50
50
|
```
|
|
51
51
|
|
|
@@ -75,9 +75,10 @@ options:
|
|
|
75
75
|
2. AskUserQuestion으로 프로필/alias 선택:
|
|
76
76
|
```
|
|
77
77
|
options:
|
|
78
|
-
- label: "
|
|
79
|
-
- label: "
|
|
80
|
-
- label: "
|
|
78
|
+
- label: "gpt6_sol_high" → 구현과 리뷰 기본값 (Recommended)
|
|
79
|
+
- label: "gpt6_sol_med" → 정리와 리팩터 균형형
|
|
80
|
+
- label: "gpt6_luna_high" → 명확한 반복 작업
|
|
81
|
+
- label: "gpt6_luna_low" → 지연 우선 경량
|
|
81
82
|
- label: "gpt6_astra_xhigh" → 고강도 추론
|
|
82
83
|
- label: "gpt6_astra_max" → 최난도 단일 작업
|
|
83
84
|
- label: "gpt6_astra_ultra" → 최상위 자동 위임 (비중첩 전용)
|
|
@@ -187,12 +188,13 @@ options:
|
|
|
187
188
|
|
|
188
189
|
| 프로필 | 용도 |
|
|
189
190
|
|------|------|
|
|
190
|
-
|
|
|
191
|
+
| gpt6_sol_high | 구현과 리뷰 기본값 |
|
|
191
192
|
| gpt6_astra_xhigh | 고강도 추론 |
|
|
192
193
|
| gpt6_astra_max | 최난도 단일 작업 |
|
|
193
194
|
| gpt6_astra_ultra | 최상위 자동 위임 (비중첩 전용) |
|
|
194
|
-
|
|
|
195
|
-
|
|
|
195
|
+
| gpt6_sol_med | 정리와 리팩터 균형형 |
|
|
196
|
+
| gpt6_luna_high | 명확한 반복 작업 |
|
|
197
|
+
| gpt6_luna_low | 지연 우선 경량 |
|
|
196
198
|
| custom | 사용자가 관리하는 프로필 |
|
|
197
199
|
|
|
198
200
|
### Codex Effort 레벨
|
|
@@ -112,7 +112,7 @@ Bash("triflux setup")
|
|
|
112
112
|
#### 단계 3: Codex 프로파일
|
|
113
113
|
|
|
114
114
|
`~/.codex/config.toml`을 Read 도구로 읽어 필수 프로파일 존재 여부 확인.
|
|
115
|
-
필수: `gpt6_astra_ultra`, `gpt6_astra_max`, `gpt6_astra_xhigh`, `
|
|
115
|
+
필수: `gpt6_astra_ultra`, `gpt6_astra_max`, `gpt6_astra_xhigh`, `gpt6_sol_high`, `gpt6_sol_med`, `gpt6_luna_high`, `gpt6_luna_low`.
|
|
116
116
|
|
|
117
117
|
- 모두 존재 → ✅ 표시
|
|
118
118
|
- 누락 있으면 → AskUserQuestion:
|