triflux 10.20.2 → 10.21.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/CLAUDE.md +174 -0
- package/bin/tfx-doctor-tui.mjs +0 -0
- package/bin/tfx-doctor.mjs +0 -0
- package/bin/tfx-profile.mjs +0 -0
- package/bin/tfx-setup-tui.mjs +0 -0
- package/bin/tfx-setup.mjs +0 -0
- package/bin/triflux.mjs +58 -3
- package/hooks/keyword-rules.json +1 -1
- package/hub/cli-adapter-base.mjs +34 -1
- package/hub/codex-adapter.mjs +4 -0
- package/hub/dynamic-routing-engine.mjs +511 -0
- package/hub/lib/prompt-tmp.mjs +33 -0
- package/hub/lib/spawn-trace.mjs +5 -1
- package/hub/routing-snapshot.mjs +261 -0
- package/hub/team/conductor.mjs +67 -3
- package/hub/team/execution-mode.mjs +34 -30
- package/hub/team/headless.mjs +8 -6
- package/hub/team/launcher-template.mjs +5 -0
- package/hub/team/psmux.mjs +57 -30
- package/hub/team/swarm-hypervisor.mjs +59 -0
- package/hub/team/tui-viewer.mjs +2 -1
- package/package.json +23 -67
- package/references/cli-parameter-reference.md +240 -0
- package/references/codex-plugin-cc-analysis.md +706 -0
- package/references/codex-plugin-cc-code-patterns.md +468 -0
- package/scripts/__tests__/setup-cleanup-stale-skills.test.mjs +19 -1
- package/scripts/check-codex-config-stable.mjs +166 -38
- package/scripts/doctor-dynamic-routing.mjs +87 -0
- package/scripts/lib/dynamic-route-cli.mjs +107 -0
- package/scripts/lib/psmux-info.mjs +59 -4
- package/scripts/pack.mjs +3 -0
- package/scripts/session-spawn-helper.mjs +2 -1
- package/scripts/setup.mjs +12 -4
- package/scripts/tfx-route.sh +36 -0
- package/skills/tfx-auto/SKILL.md +8 -8
- package/skills/tfx-auto/SKILL.md.tmpl +8 -8
- package/.claude-plugin/marketplace.json +0 -34
- package/.claude-plugin/plugin.json +0 -22
- package/config/mcp-registry.json +0 -44
- package/tui/codex-profile.mjs +0 -459
- package/tui/core.mjs +0 -266
- package/tui/doctor.mjs +0 -375
- package/tui/gemini-profile.mjs +0 -299
- package/tui/monitor-data.mjs +0 -152
- package/tui/monitor.mjs +0 -317
- package/tui/setup.mjs +0 -599
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# triflux — Claude Code 운영 가이드
|
|
2
|
+
|
|
3
|
+
<core-systems>
|
|
4
|
+
## 핵심 스킬 시스템 (항상 인지)
|
|
5
|
+
|
|
6
|
+
이 프로젝트는 3개의 스킬 시스템을 동시에 사용한다. 어떤 작업이든 해당 시스템의 스킬이 있는지 먼저 확인한다.
|
|
7
|
+
|
|
8
|
+
| 시스템 | 접두사 | 용도 | 스킬 수 |
|
|
9
|
+
|--------|--------|------|---------|
|
|
10
|
+
| **triflux** | `/tfx-*` | CLI 라우팅, 멀티모델 오케스트레이션, 스웜, 원격 실행 | ~40개 |
|
|
11
|
+
| **gstack** | `/` (접두사 없음) | QA, ship, investigate, design, review, checkpoint | ~35개 |
|
|
12
|
+
| **omc** | `/oh-my-claudecode:*` | autopilot, ralph, team, ultrawork, ccg | ~25개 |
|
|
13
|
+
|
|
14
|
+
스킬을 모르면 자연어 라우팅(`.claude/rules/tfx-routing.md`)으로 자동 매핑된다.
|
|
15
|
+
세션 종료 전 메모리 파일이 3개+ 변경됐으면 `/memory-hygiene` 제안을 검토한다.
|
|
16
|
+
</core-systems>
|
|
17
|
+
|
|
18
|
+
<psmux-wt>
|
|
19
|
+
## psmux/WT 규칙
|
|
20
|
+
|
|
21
|
+
psmux 세션·WT 패인을 생성/조작/정리할 때 `tfx-psmux-rules` 스킬을 참조한다.
|
|
22
|
+
WT 프리징 방지: exit → sleep 2 → kill 순서. 바로 kill하지 않는다.
|
|
23
|
+
|
|
24
|
+
### wt.exe → wt-manager 경유
|
|
25
|
+
|
|
26
|
+
safety-guard가 `wt.exe`, `wt new-tab`, `wt split-pane`, `Start-Process wt`를 차단한다.
|
|
27
|
+
`hub/team/wt-manager.mjs`의 API를 사용한다.
|
|
28
|
+
|
|
29
|
+
| 용도 | API |
|
|
30
|
+
|------|-----|
|
|
31
|
+
| 새 탭 | `createTab({ title, command, profile, cwd })` |
|
|
32
|
+
| 패인 분할 | `splitPane({ direction: 'H'\|'V', title, command })` |
|
|
33
|
+
| 다중 배치 | `applySplitLayout([{ title, command, direction }])` |
|
|
34
|
+
| 탭 정리 | `closeTab(title)` / `closeStale({ olderThanMs, titlePattern })` |
|
|
35
|
+
|
|
36
|
+
차단과 대안은 항상 쌍으로 존재해야 한다. 차단만 추가하고 대안을 안 만들면 데드락.
|
|
37
|
+
|
|
38
|
+
### raw `psmux kill-session` → psmux wrapper 경유
|
|
39
|
+
|
|
40
|
+
safety-guard가 raw `psmux kill-session`을 차단한다.
|
|
41
|
+
세션 정리는 `hub/team/psmux.mjs` 공개 API 또는 internal wrapper로 우회한다.
|
|
42
|
+
|
|
43
|
+
| 용도 | API / 래퍼 |
|
|
44
|
+
|------|------------|
|
|
45
|
+
| 세션 조회 | `listSessions({ filterTitle?, olderThanMs? })` |
|
|
46
|
+
| title prefix / regex kill | `killSessionByTitle(titlePattern)` |
|
|
47
|
+
| stale idle 세션 정리 | `pruneStale({ olderThanMs, dryRun })` |
|
|
48
|
+
| Bash 훅 우회용 래퍼 | `node hub/team/psmux.mjs --internal kill-by-title <prefix\|/regex/>` |
|
|
49
|
+
|
|
50
|
+
### psmux에서 Codex 실행
|
|
51
|
+
|
|
52
|
+
| 방식 | 동작 | 이유 |
|
|
53
|
+
|------|------|------|
|
|
54
|
+
| `codex` (interactive) | 불가 | psmux에서 TTY를 못 잡음 |
|
|
55
|
+
| `codex < prompt.md` | 불가 | "stdin is not a terminal" |
|
|
56
|
+
| `codex exec "$(cat prompt.md)" -s danger-full-access --dangerously-bypass-approvals-and-sandbox` | 사용 | 유일한 안전 경로 |
|
|
57
|
+
|
|
58
|
+
`codex exec`는 config.toml `approval_mode`를 무시하므로 `--dangerously-bypass-approvals-and-sandbox` 필수.
|
|
59
|
+
`-s` 유효값: read-only, workspace-write, danger-full-access.
|
|
60
|
+
</psmux-wt>
|
|
61
|
+
|
|
62
|
+
<codex-config>
|
|
63
|
+
## Codex config.toml
|
|
64
|
+
|
|
65
|
+
config.toml에 이미 설정된 값은 CLI 플래그로 중복 지정하지 않는다.
|
|
66
|
+
|
|
67
|
+
| config.toml에 있으면 | CLI에서 생략 |
|
|
68
|
+
|---------------------|-------------|
|
|
69
|
+
| `approval_mode = "auto"` | `-a`, `--full-auto` |
|
|
70
|
+
| `sandbox = "workspace-write"` | `-s`, `--full-auto` |
|
|
71
|
+
|
|
72
|
+
안전 패턴: config.toml에 기본값을 두고, CLI에서는 `--profile` 선택만 한다.
|
|
73
|
+
</codex-config>
|
|
74
|
+
|
|
75
|
+
<account-broker>
|
|
76
|
+
## AccountBroker (계정 브로커)
|
|
77
|
+
|
|
78
|
+
conductor, headless, swarm-hypervisor가 하나의 AccountBroker 싱글턴을 공유한다.
|
|
79
|
+
|
|
80
|
+
| 항목 | 설명 |
|
|
81
|
+
|------|------|
|
|
82
|
+
| 계정별 CircuitBreaker | 장애 격리 — 한 계정 오류가 다른 계정에 전파되지 않음 |
|
|
83
|
+
| busy 플래그 | 동일 계정 이중 임대(double-lease) 방지 |
|
|
84
|
+
| `/broker/reload` | 장시간 세션 중 accounts.json 핫리로드. active lease ownership은 reload 후에도 보존 |
|
|
85
|
+
| Adapter no-lease 정책 | headless adapter는 broker가 비활성/empty이면 기본 CLI auth path로 실행, broker가 enabled인데 lease가 없으면 `circuit_open` 실패 |
|
|
86
|
+
| Conductor no-lease 정책 | 로컬 Conductor 세션은 `broker_no_lease`를 event log에 남기고 spawn은 계속 진행. accountId가 없으므로 release는 호출하지 않음 |
|
|
87
|
+
| Public snapshot 정책 | `/broker/snapshot`과 dashboard는 `publicSnapshot()`만 사용. `env`, `authFile`, `profile`, `host`, 파일 경로, raw failure timestamp는 공개하지 않음 |
|
|
88
|
+
| Diagnostic 이벤트 | `securityViolation`, `authSyncError`는 hub가 redacted warn 로그(`broker.security_violation`, `broker.auth_sync_error`)로 처리 |
|
|
89
|
+
| EventEmitter 이벤트 | `lease`, `release`, `cooldown`, `tierFallback`, `circuitOpen`, `circuitClose`, `noAvailableAccounts` — HUD 연동용 |
|
|
90
|
+
</account-broker>
|
|
91
|
+
|
|
92
|
+
<remote>
|
|
93
|
+
## 원격 실행
|
|
94
|
+
|
|
95
|
+
### 스킬 구분
|
|
96
|
+
|
|
97
|
+
| 스킬 | 대상 | 방식 |
|
|
98
|
+
|------|------|------|
|
|
99
|
+
| tfx-codex-swarm | 로컬 전용 | 로컬 worktree + psmux |
|
|
100
|
+
| tfx-remote-spawn | Claude Code 원격 | SSH → Claude Code 세션 → 내부 tfx 라우팅 |
|
|
101
|
+
|
|
102
|
+
codex를 SSH 너머로 직접 실행하지 않는다. config.toml 충돌 + TTY 문제.
|
|
103
|
+
원격에서 codex가 필요하면: remote-spawn → Claude Code → Claude가 내부에서 codex 호출.
|
|
104
|
+
|
|
105
|
+
### SSH 패턴
|
|
106
|
+
|
|
107
|
+
hosts.json `os` 필드로 대상 셸을 판단한다. safety-guard도 이 필드를 참조.
|
|
108
|
+
|
|
109
|
+
| 대상 OS | 셸 | 패턴 |
|
|
110
|
+
|---------|-----|------|
|
|
111
|
+
| windows | PowerShell | scp + `pwsh -File` 필수. `$var` → `$env:VAR`, `2>/dev/null` → `2>$null` |
|
|
112
|
+
| darwin | zsh | 인라인 가능. brew PATH 주의 (`/opt/homebrew/bin`) |
|
|
113
|
+
| linux | bash | 인라인 가능. 표준 POSIX |
|
|
114
|
+
|
|
115
|
+
- `~` → `$HOME` 변환은 모든 OS 공통
|
|
116
|
+
</remote>
|
|
117
|
+
|
|
118
|
+
<headless-retrieval>
|
|
119
|
+
## Headless 결과 회수
|
|
120
|
+
|
|
121
|
+
background로 실행한 headless 결과는 **반드시 task-notification 완료 후** 읽는다.
|
|
122
|
+
|
|
123
|
+
| 패턴 | 올바름 | 이유 |
|
|
124
|
+
|------|--------|------|
|
|
125
|
+
| task-notification 후 output 파일 읽기 | YES | 프로세스 종료 = 워커 전부 완료 |
|
|
126
|
+
| task-notification 전 output 파일 tail | NO | 시작 메시지만 보이고 "실패"로 오진 |
|
|
127
|
+
| psmux capture-pane으로 중간 체크 | NO | 워커 진행 중이면 빈 화면일 수 있음 |
|
|
128
|
+
|
|
129
|
+
완료 마커: `=== HEADLESS_COMPLETE succeeded=N failed=N total=N ===`
|
|
130
|
+
워커 상세: `$TMPDIR/tfx-headless/{sessionName}-worker-N.txt`
|
|
131
|
+
</headless-retrieval>
|
|
132
|
+
|
|
133
|
+
<cross-review>
|
|
134
|
+
## 교차 검증
|
|
135
|
+
|
|
136
|
+
- Claude 작성 코드 → Codex 리뷰
|
|
137
|
+
- Codex 작성 코드 → Claude 리뷰
|
|
138
|
+
- 동일 모델 self-approve 하지 않는다
|
|
139
|
+
- git commit 전 미검증 파일 감지 시 nudge
|
|
140
|
+
</cross-review>
|
|
141
|
+
|
|
142
|
+
<session-context>
|
|
143
|
+
## 맥락 이탈 판단
|
|
144
|
+
|
|
145
|
+
현재 세션 맥락과 무관한 요청이 감지되면 psmux 격리를 제안한다.
|
|
146
|
+
|
|
147
|
+
| 확신도 | 신호 | 행동 |
|
|
148
|
+
|--------|------|------|
|
|
149
|
+
| 확실 | "새 탭", "별도로", "새 세션" | 바로 psmux spawn |
|
|
150
|
+
| 높음 | 다른 프로젝트/스택 언급 | 분리 제안 |
|
|
151
|
+
| 중간 | 작업 유형 전환 | 분리 제안 + 현재 세션 옵션 |
|
|
152
|
+
| 낮음 | 현재 작업 연장 | 세션 유지 |
|
|
153
|
+
</session-context>
|
|
154
|
+
|
|
155
|
+
## 세부 규칙은 `.claude/rules/` 참조
|
|
156
|
+
|
|
157
|
+
| 파일 | 내용 |
|
|
158
|
+
|------|------|
|
|
159
|
+
| `.claude/rules/tfx-routing.md` | 자연어 → 스킬 라우팅, CLI 라우팅 Layer 1~3, 충돌 해소 |
|
|
160
|
+
| `.claude/rules/tfx-execution-skill-map.md` | tfx-auto / multi / swarm 실행 엔진 매핑, 격리 기준, 안티패턴 |
|
|
161
|
+
| `.claude/rules/tfx-autoplan-principles.md` | gstack autoplan의 6 decision principles, phase 우선순위, 충돌 해소 규칙 추출본 |
|
|
162
|
+
| `.claude/rules/tfx-update-logic.md` | triflux / OMC / gstack / Codex / Gemini 업데이트 로직 |
|
|
163
|
+
| `.claude/rules/tfx-stack-coexistence.md` | gstack / superpowers / triflux 공존 원칙, 레이어 분리, 의존 방향, 충돌 해소 |
|
|
164
|
+
| `.claude/rules/tfx-mirror-policy.md` | packages/ 3-layer mirror 정책 (core 단순 cp / remote import 변환 / triflux byte-identical), tests 제외 룰, drift 차단 |
|
|
165
|
+
|
|
166
|
+
Claude Code는 `.claude/rules/*.md` 를 자동 로드한다. Codex CLI는 `@import` 미지원이므로 필요 시 `AGENTS.md` 를 독립 유지한다.
|
|
167
|
+
|
|
168
|
+
## GBrain Configuration (configured by /setup-gbrain)
|
|
169
|
+
- Engine: pglite
|
|
170
|
+
- Config file: ~/.gbrain/config.json (mode 0600)
|
|
171
|
+
- Setup date: 2026-04-25
|
|
172
|
+
- MCP registered: yes (user scope, absolute path)
|
|
173
|
+
- Memory sync: artifacts-only (repo: github.com/tellang/gstack-brain-tellang)
|
|
174
|
+
- Current repo policy: read-write (github.com/tellang/triflux)
|
package/bin/tfx-doctor-tui.mjs
CHANGED
|
File without changes
|
package/bin/tfx-doctor.mjs
CHANGED
|
File without changes
|
package/bin/tfx-profile.mjs
CHANGED
|
File without changes
|
package/bin/tfx-setup-tui.mjs
CHANGED
|
File without changes
|
package/bin/tfx-setup.mjs
CHANGED
|
File without changes
|
package/bin/triflux.mjs
CHANGED
|
@@ -77,6 +77,7 @@ import {
|
|
|
77
77
|
getVersion,
|
|
78
78
|
getWindowsHubAutostartStatus,
|
|
79
79
|
hasProfileSection,
|
|
80
|
+
isLocalDevSkillDir,
|
|
80
81
|
isSetupUserStateFile,
|
|
81
82
|
LEGACY_CODEX_MODELS,
|
|
82
83
|
REQUIRED_CODEX_PROFILES,
|
|
@@ -150,7 +151,7 @@ const CLI_COMMAND_SCHEMAS = Object.freeze({
|
|
|
150
151
|
},
|
|
151
152
|
doctor: {
|
|
152
153
|
usage:
|
|
153
|
-
"tfx doctor [--fix] [--reset] [--audit] [--diagnose] [--purge-logs] [--json]",
|
|
154
|
+
"tfx doctor [--fix] [--reset] [--audit] [--diagnose] [--purge-logs] [--dynamic-routing] [--json]",
|
|
154
155
|
description: "설치 상태 진단 및 자동 복구",
|
|
155
156
|
options: [
|
|
156
157
|
{
|
|
@@ -180,6 +181,12 @@ const CLI_COMMAND_SCHEMAS = Object.freeze({
|
|
|
180
181
|
description:
|
|
181
182
|
"--fix 와 함께 사용. cli-issues.jsonl 에서 7일 초과 항목 물리 삭제 (#144)",
|
|
182
183
|
},
|
|
184
|
+
{
|
|
185
|
+
name: "--dynamic-routing",
|
|
186
|
+
type: "boolean",
|
|
187
|
+
description:
|
|
188
|
+
"Phase 1 dynamic routing 상태 진단 (env / policy / snapshot cache / preview decision)",
|
|
189
|
+
},
|
|
183
190
|
{
|
|
184
191
|
name: "--json",
|
|
185
192
|
type: "boolean",
|
|
@@ -583,15 +590,22 @@ function which(cmd) {
|
|
|
583
590
|
}
|
|
584
591
|
|
|
585
592
|
function whichInShell(cmd, shell) {
|
|
593
|
+
const escapedCmd = cmd.replace(/(["\\$`])/g, "\\$1");
|
|
586
594
|
const shellArgs = {
|
|
587
595
|
bash: [
|
|
588
596
|
"bash",
|
|
589
|
-
[
|
|
597
|
+
[
|
|
598
|
+
"-lc",
|
|
599
|
+
`source ~/.bashrc 2>/dev/null || true; command -v "${escapedCmd}" 2>/dev/null`,
|
|
600
|
+
],
|
|
590
601
|
],
|
|
591
602
|
cmd: ["cmd", ["/c", "where", cmd]],
|
|
592
603
|
zsh: [
|
|
593
604
|
"zsh",
|
|
594
|
-
[
|
|
605
|
+
[
|
|
606
|
+
"-lc",
|
|
607
|
+
`source ~/.zshrc 2>/dev/null || true; command -v "${escapedCmd}" 2>/dev/null`,
|
|
608
|
+
],
|
|
595
609
|
],
|
|
596
610
|
pwsh: [
|
|
597
611
|
"pwsh",
|
|
@@ -626,6 +640,7 @@ function isDevUpdateRequested(argv = process.argv) {
|
|
|
626
640
|
function checkShellAvailable(shell) {
|
|
627
641
|
const cmds = {
|
|
628
642
|
bash: "bash --version",
|
|
643
|
+
zsh: "zsh --version",
|
|
629
644
|
cmd: "cmd /c echo ok",
|
|
630
645
|
pwsh: "pwsh -NoProfile -c echo ok",
|
|
631
646
|
};
|
|
@@ -2701,6 +2716,7 @@ async function cmdDoctor(options = {}) {
|
|
|
2701
2716
|
|
|
2702
2717
|
for (const n of readdirSync(userSkillsDir)) {
|
|
2703
2718
|
if (!n.startsWith("tfx-")) continue;
|
|
2719
|
+
if (isLocalDevSkillDir(join(userSkillsDir, n))) continue;
|
|
2704
2720
|
if (!pkgSkills.has(n)) staleSkills.push(n);
|
|
2705
2721
|
}
|
|
2706
2722
|
}
|
|
@@ -5954,6 +5970,45 @@ async function main() {
|
|
|
5954
5970
|
}
|
|
5955
5971
|
return;
|
|
5956
5972
|
}
|
|
5973
|
+
if (cmdArgs.includes("--dynamic-routing")) {
|
|
5974
|
+
const { diagnoseDynamicRouting } = await import(
|
|
5975
|
+
"../scripts/doctor-dynamic-routing.mjs"
|
|
5976
|
+
);
|
|
5977
|
+
const report = await diagnoseDynamicRouting();
|
|
5978
|
+
if (JSON_OUTPUT) {
|
|
5979
|
+
console.log(JSON.stringify(report, null, 2));
|
|
5980
|
+
} else {
|
|
5981
|
+
const mark = (b) =>
|
|
5982
|
+
b ? `${GREEN_BRIGHT}✓${RESET}` : `${RED}✗${RESET}`;
|
|
5983
|
+
console.log(
|
|
5984
|
+
`\n ${AMBER}${BOLD}⬡ triflux doctor — dynamic routing${RESET}\n`,
|
|
5985
|
+
);
|
|
5986
|
+
console.log(
|
|
5987
|
+
` ${mark(report.enabled)} enabled: ${report.enabled} (TRIFLUX_DYNAMIC_ROUTING=${report.envFlag ?? "미설정"})`,
|
|
5988
|
+
);
|
|
5989
|
+
console.log(
|
|
5990
|
+
` ${mark(report.policyLoaded)} policy loaded: ${report.policyLoaded} (scenarios=${report.policyScenarios.length})`,
|
|
5991
|
+
);
|
|
5992
|
+
console.log(
|
|
5993
|
+
` ${mark(report.snapshotCached)} snapshot cache: ${
|
|
5994
|
+
report.snapshotCached
|
|
5995
|
+
? `hit (age=${report.snapshotAgeMs}ms)`
|
|
5996
|
+
: "miss"
|
|
5997
|
+
}`,
|
|
5998
|
+
);
|
|
5999
|
+
if (report.previewDecision) {
|
|
6000
|
+
const d = report.previewDecision;
|
|
6001
|
+
console.log(
|
|
6002
|
+
` ${GREEN_BRIGHT}→${RESET} preview decision: scenario=${d.scenario}, mode=${d.mode}, lane=${d.lane}, shards[0].cli=${d.shards?.[0]?.cli ?? "?"}`,
|
|
6003
|
+
);
|
|
6004
|
+
}
|
|
6005
|
+
if (report.error) {
|
|
6006
|
+
console.log(` ${RED}✗${RESET} error: ${report.error}`);
|
|
6007
|
+
}
|
|
6008
|
+
console.log("");
|
|
6009
|
+
}
|
|
6010
|
+
return;
|
|
6011
|
+
}
|
|
5957
6012
|
const fix = cmdArgs.includes("--fix");
|
|
5958
6013
|
const reset = cmdArgs.includes("--reset");
|
|
5959
6014
|
const purgeLogs = cmdArgs.includes("--purge-logs");
|
package/hooks/keyword-rules.json
CHANGED
package/hub/cli-adapter-base.mjs
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { execSync, spawn } from "node:child_process";
|
|
5
5
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
6
6
|
|
|
7
|
+
import { writePromptToTmpFile } from "./lib/prompt-tmp.mjs";
|
|
7
8
|
import { IS_WINDOWS, killProcess } from "./platform.mjs";
|
|
8
9
|
|
|
9
10
|
// ── Quota retry-after 파싱 ──────────────────────────────────────
|
|
@@ -145,9 +146,20 @@ export const CODEX_MCP_EXECUTION_EXIT_CODE = 1;
|
|
|
145
146
|
|
|
146
147
|
/**
|
|
147
148
|
* long-form 플래그 기반 명령 빌더.
|
|
149
|
+
*
|
|
150
|
+
* macOS 회귀 fix (codex v0.130.0 oh-my-codex hook lifecycle): 매우 긴
|
|
151
|
+
* argv-inline prompt 가 SessionStart Failed 를 유발하므로 default 가 prompt
|
|
152
|
+
* 를 stdin 으로 전달하는 패턴. 셸별 분기:
|
|
153
|
+
* - Unix (bash/zsh): `codex exec ... < '/tmp/triflux-codex-prompt/prompt-*.txt'`
|
|
154
|
+
* - Windows (pwsh7): `Get-Content -Raw 'path' | codex exec ...`
|
|
155
|
+
* (pwsh7 의 `<` 는 reserved future syntax 라 호환성 보장 안 됨)
|
|
156
|
+
*
|
|
157
|
+
* 환경 변수 `TFX_CODEX_STDIN_PROMPT=0` 이면 legacy argv-inline 으로 회귀.
|
|
158
|
+
* 결정론 보장이 필요한 launcher path 는 호출 시 `stdinPrompt: false` 명시.
|
|
159
|
+
*
|
|
148
160
|
* @param {string} prompt
|
|
149
161
|
* @param {string|null} resultFile — null이면 --output-last-message 생략
|
|
150
|
-
* @param {{ profile?: string, skipGitRepoCheck?: boolean, sandboxBypass?: boolean, cwd?: string, mcpServers?: string[] }} [opts]
|
|
162
|
+
* @param {{ profile?: string, skipGitRepoCheck?: boolean, sandboxBypass?: boolean, cwd?: string, mcpServers?: string[], stdinPrompt?: boolean }} [opts]
|
|
151
163
|
* @returns {string} 실행할 셸 커맨드
|
|
152
164
|
*/
|
|
153
165
|
export function buildExecCommand(prompt, resultFile = null, opts = {}) {
|
|
@@ -156,6 +168,7 @@ export function buildExecCommand(prompt, resultFile = null, opts = {}) {
|
|
|
156
168
|
skipGitRepoCheck = true,
|
|
157
169
|
sandboxBypass = true,
|
|
158
170
|
mcpServers,
|
|
171
|
+
stdinPrompt,
|
|
159
172
|
} = opts;
|
|
160
173
|
|
|
161
174
|
const parts = ["codex"];
|
|
@@ -183,10 +196,30 @@ export function buildExecCommand(prompt, resultFile = null, opts = {}) {
|
|
|
183
196
|
if (skipGitRepoCheck) parts.push("--skip-git-repo-check");
|
|
184
197
|
}
|
|
185
198
|
|
|
199
|
+
const useStdin = resolveStdinPromptMode(stdinPrompt);
|
|
200
|
+
const hasPrompt = typeof prompt === "string" && prompt.length > 0;
|
|
201
|
+
if (useStdin && hasPrompt) {
|
|
202
|
+
const promptFile = writePromptToTmpFile(prompt);
|
|
203
|
+
if (IS_WINDOWS) {
|
|
204
|
+
// pwsh7 호환: `<` redirect 는 reserved future syntax 라 동작 불안정.
|
|
205
|
+
// Get-Content -Raw stdin pipe 로 prompt 를 codex stdin 에 주입.
|
|
206
|
+
return `Get-Content -Raw '${escapePwshSingleQuoted(promptFile)}' | ${parts.join(" ")}`;
|
|
207
|
+
}
|
|
208
|
+
return `${parts.join(" ")} < ${shellQuote(promptFile)}`;
|
|
209
|
+
}
|
|
210
|
+
|
|
186
211
|
parts.push(JSON.stringify(prompt));
|
|
187
212
|
return parts.join(" ");
|
|
188
213
|
}
|
|
189
214
|
|
|
215
|
+
function resolveStdinPromptMode(explicit) {
|
|
216
|
+
if (typeof explicit === "boolean") return explicit;
|
|
217
|
+
// Default ON to fix macOS codex hook regression. Opt out via env=0.
|
|
218
|
+
const env = process.env.TFX_CODEX_STDIN_PROMPT;
|
|
219
|
+
if (env === "0" || env === "false") return false;
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
|
|
190
223
|
// ── Sleep ───────────────────────────────────────────────────────
|
|
191
224
|
|
|
192
225
|
export function sleep(ms) {
|
package/hub/codex-adapter.mjs
CHANGED
|
@@ -108,6 +108,10 @@ export function buildExecArgs(opts = {}) {
|
|
|
108
108
|
skipGitRepoCheck: true,
|
|
109
109
|
sandboxBypass: true,
|
|
110
110
|
cwd: opts.cwd,
|
|
111
|
+
// stdinPrompt 가 명시되면 buildExecCommand 로 forward.
|
|
112
|
+
// headless 워커 spawn (backend.mjs) 은 default (stdin on, macOS hook fix).
|
|
113
|
+
// launcher path (launcher-template.mjs) 는 결정론 위해 false 전달.
|
|
114
|
+
stdinPrompt: opts.stdinPrompt,
|
|
111
115
|
});
|
|
112
116
|
|
|
113
117
|
if (!prompt) return command.replace(/\s+""$/u, "");
|