triflux 6.0.1 → 6.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/hub/team/headless.mjs +14 -9
- package/package.json +1 -1
- package/skills/tfx-auto/SKILL.md +17 -13
- package/skills/tfx-multi/SKILL.md +13 -76
package/hub/team/headless.mjs
CHANGED
|
@@ -81,13 +81,18 @@ export async function runHeadless(sessionName, assignments, opts = {}) {
|
|
|
81
81
|
|
|
82
82
|
mkdirSync(RESULT_DIR, { recursive: true });
|
|
83
83
|
|
|
84
|
+
// onProgress 예외를 삼켜 실행 흐름 보호 (onPoll과 동일 패턴)
|
|
85
|
+
const safeProgress = onProgress
|
|
86
|
+
? (event) => { try { onProgress(event); } catch { /* 콜백 예외 삼킴 */ } }
|
|
87
|
+
: null;
|
|
88
|
+
|
|
84
89
|
let dispatches;
|
|
85
90
|
|
|
86
91
|
if (progressive) {
|
|
87
92
|
// ─── 실시간 스플릿 모드: lead pane만 생성 후, 워커를 하나씩 추가 ───
|
|
88
93
|
const session = createPsmuxSession(sessionName, { layout, paneCount: 1 });
|
|
89
94
|
applyTrifluxTheme(sessionName);
|
|
90
|
-
if (
|
|
95
|
+
if (safeProgress) safeProgress({ type: "session_created", sessionName, panes: session.panes });
|
|
91
96
|
|
|
92
97
|
dispatches = assignments.map((assignment, i) => {
|
|
93
98
|
const paneName = `worker-${i + 1}`;
|
|
@@ -104,7 +109,7 @@ export async function runHeadless(sessionName, assignments, opts = {}) {
|
|
|
104
109
|
// 타이틀 설정
|
|
105
110
|
try { psmuxExec(["select-pane", "-t", newPaneId, "-T", paneTitle]); } catch { /* 무시 */ }
|
|
106
111
|
|
|
107
|
-
if (
|
|
112
|
+
if (safeProgress) safeProgress({ type: "worker_added", paneName, cli: assignment.cli, paneTitle });
|
|
108
113
|
|
|
109
114
|
// 캡처 시작 + 명령 dispatch (paneId 직접 사용 — resolvePane race 회피)
|
|
110
115
|
const resultFile = join(RESULT_DIR, `${sessionName}-${paneName}.txt`).replace(/\\/g, "/");
|
|
@@ -112,7 +117,7 @@ export async function runHeadless(sessionName, assignments, opts = {}) {
|
|
|
112
117
|
startCapture(sessionName, newPaneId);
|
|
113
118
|
const dispatch = dispatchCommand(sessionName, newPaneId, cmd);
|
|
114
119
|
|
|
115
|
-
if (
|
|
120
|
+
if (safeProgress) safeProgress({ type: "dispatched", paneName, cli: assignment.cli });
|
|
116
121
|
|
|
117
122
|
return { ...dispatch, paneId: newPaneId, paneName, resultFile, cli: assignment.cli, role: assignment.role };
|
|
118
123
|
});
|
|
@@ -125,7 +130,7 @@ export async function runHeadless(sessionName, assignments, opts = {}) {
|
|
|
125
130
|
const paneCount = assignments.length + 1;
|
|
126
131
|
const session = createPsmuxSession(sessionName, { layout, paneCount });
|
|
127
132
|
applyTrifluxTheme(sessionName);
|
|
128
|
-
if (
|
|
133
|
+
if (safeProgress) safeProgress({ type: "session_created", sessionName, panes: session.panes });
|
|
129
134
|
|
|
130
135
|
dispatches = assignments.map((assignment, i) => {
|
|
131
136
|
const paneName = `worker-${i + 1}`;
|
|
@@ -137,7 +142,7 @@ export async function runHeadless(sessionName, assignments, opts = {}) {
|
|
|
137
142
|
// 리네임하면 waitForCompletion이 "codex (role).log"를 찾지만 실제는 "worker-N.log"로 불일치
|
|
138
143
|
// progressive 모드에서는 split-window 시 새 pane에 바로 타이틀이 설정되므로 문제없음
|
|
139
144
|
|
|
140
|
-
if (
|
|
145
|
+
if (safeProgress) safeProgress({ type: "dispatched", paneName, cli: assignment.cli });
|
|
141
146
|
|
|
142
147
|
return { ...dispatch, paneName, resultFile, cli: assignment.cli, role: assignment.role };
|
|
143
148
|
});
|
|
@@ -147,14 +152,14 @@ export async function runHeadless(sessionName, assignments, opts = {}) {
|
|
|
147
152
|
const results = await Promise.all(dispatches.map(async (d) => {
|
|
148
153
|
// onPoll → onProgress 변환 (throttle by progressIntervalSec)
|
|
149
154
|
const pollOpts = {};
|
|
150
|
-
if (
|
|
155
|
+
if (safeProgress && progressIntervalSec > 0) {
|
|
151
156
|
let lastProgressAt = 0;
|
|
152
157
|
const intervalMs = progressIntervalSec * 1000;
|
|
153
158
|
pollOpts.onPoll = ({ content }) => {
|
|
154
159
|
const now = Date.now();
|
|
155
160
|
if (now - lastProgressAt >= intervalMs) {
|
|
156
161
|
lastProgressAt = now;
|
|
157
|
-
|
|
162
|
+
safeProgress({
|
|
158
163
|
type: "progress",
|
|
159
164
|
paneName: d.paneName,
|
|
160
165
|
cli: d.cli,
|
|
@@ -170,8 +175,8 @@ export async function runHeadless(sessionName, assignments, opts = {}) {
|
|
|
170
175
|
? readResult(d.resultFile, d.paneId)
|
|
171
176
|
: "";
|
|
172
177
|
|
|
173
|
-
if (
|
|
174
|
-
|
|
178
|
+
if (safeProgress) {
|
|
179
|
+
safeProgress({
|
|
175
180
|
type: "completed",
|
|
176
181
|
paneName: d.paneName,
|
|
177
182
|
cli: d.cli,
|
package/package.json
CHANGED
package/skills/tfx-auto/SKILL.md
CHANGED
|
@@ -150,28 +150,32 @@ Plan/PRD/Approval은 tfx-auto에서 실행, 그 후 tfx-multi Phase 3로 전환.
|
|
|
150
150
|
## 멀티 태스크 라우팅 (트리아지 후)
|
|
151
151
|
|
|
152
152
|
> **트리아지 결과에 따라 실행 경로 결정.**
|
|
153
|
+
> v6.0.0부터 CLI 워커는 **Lead-Direct Headless** (psmux)가 기본. Agent 래퍼 불필요.
|
|
153
154
|
|
|
154
|
-
| 조건 | 실행 경로 |
|
|
155
|
+
| 조건 | 실행 경로 | 엔진 |
|
|
155
156
|
|------|----------|------|
|
|
156
|
-
| 1개 + quick | tfx-auto 직접 실행 (fire-and-forget) |
|
|
157
|
-
| 1개 + thorough | tfx-auto 직접 실행 + verify/fix loop |
|
|
158
|
-
| 2개+ + quick |
|
|
159
|
-
| 2개+ + thorough | Plan/PRD/Approval 후 →
|
|
157
|
+
| 1개 + quick | tfx-auto 직접 실행 (fire-and-forget) | tfx-route.sh |
|
|
158
|
+
| 1개 + thorough | tfx-auto 직접 실행 + verify/fix loop | tfx-route.sh |
|
|
159
|
+
| 2개+ + quick | **headless 직접 실행** (WT 자동 팝업) | headless.mjs |
|
|
160
|
+
| 2개+ + thorough | Plan/PRD/Approval 후 → headless + verify/fix | headless.mjs |
|
|
161
|
+
| psmux 미설치 fallback | Native Teams (Agent slim wrapper) | native.mjs |
|
|
160
162
|
|
|
161
|
-
**전환 방법:** 트리아지 완료 후 서브태스크
|
|
162
|
-
|
|
163
|
+
**전환 방법:** 트리아지 완료 후 서브태스크 배열을 headless.runHeadlessInteractive()에 전달.
|
|
164
|
+
Windows Terminal에 psmux 세션이 자동 팝업되어 사용자가 실시간으로 CLI 출력 확인.
|
|
163
165
|
|
|
164
166
|
```
|
|
165
167
|
thorough = args에 -t 또는 --thorough 포함
|
|
166
168
|
|
|
167
169
|
if subtasks.length >= 2:
|
|
168
|
-
if
|
|
169
|
-
→
|
|
170
|
-
|
|
171
|
-
|
|
170
|
+
if psmux 설치됨:
|
|
171
|
+
→ headless.runHeadlessInteractive(assignments, {
|
|
172
|
+
autoAttach: true, // WT 자동 팝업
|
|
173
|
+
progressive: true, // 실시간 스플릿
|
|
174
|
+
progressIntervalSec: 10,
|
|
175
|
+
})
|
|
176
|
+
→ if thorough: verify → fix loop
|
|
172
177
|
else:
|
|
173
|
-
→ tfx-multi Phase 3
|
|
174
|
-
→ Phase 4 결과 수집 → Phase 5 정리
|
|
178
|
+
→ fallback: tfx-multi Phase 3 Native Teams (Agent slim wrapper)
|
|
175
179
|
else:
|
|
176
180
|
if thorough:
|
|
177
181
|
→ Pipeline init → Plan → PRD → Approval → 직접 실행 → Verify → Fix loop
|
|
@@ -73,82 +73,7 @@ preflight와 Agent 생성을 병렬로 실행하여 사용자 체감 지연을
|
|
|
73
73
|
> `--thorough`(기본) 모드에서 실행된다. `--quick` 플래그 시 건너뛴다.
|
|
74
74
|
> 상세는 → [`references/thorough-pipeline.md`](references/thorough-pipeline.md) 참조.
|
|
75
75
|
|
|
76
|
-
### Phase 3:
|
|
77
|
-
|
|
78
|
-
#### Step 3a: 팀 생성
|
|
79
|
-
|
|
80
|
-
```
|
|
81
|
-
teamName = "tfx-" + Date.now().toString(36).slice(-6)
|
|
82
|
-
TeamCreate({ team_name: teamName, description: "tfx-multi: {원본 작업 요약}" })
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
#### Step 3b: 공유 작업 등록
|
|
86
|
-
|
|
87
|
-
```
|
|
88
|
-
for each assignment (index i):
|
|
89
|
-
TaskCreate({ subject: assignment.subtask, metadata: { cli, role } })
|
|
90
|
-
agentName = "{cli}-worker-{i+1}"
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
#### Step 3c: 슬림 래퍼 Agent 실행
|
|
94
|
-
|
|
95
|
-
Codex/Gemini 서브태스크마다 슬림 래퍼 Agent를 spawn하여 Shift+Down 네비게이션에 등록한다.
|
|
96
|
-
래퍼 내부에서 `tfx-route.sh`로 CLI를 실행하고, 리드 피드백을 받아 재실행하는 구조이다.
|
|
97
|
-
|
|
98
|
-
```
|
|
99
|
-
for each item where item.cli in ["codex", "gemini"]:
|
|
100
|
-
Agent({
|
|
101
|
-
name: item.agentName,
|
|
102
|
-
team_name: teamName,
|
|
103
|
-
mode: "bypassPermissions",
|
|
104
|
-
run_in_background: true,
|
|
105
|
-
prompt: buildSlimWrapperPrompt(item.cli, { subtask, role, teamName, taskId, agentName, leadName: "team-lead", mcp_profile })
|
|
106
|
-
})
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
슬림 래퍼 프롬프트의 단일 truth source: `hub/team/native.mjs`의 `buildSlimWrapperPrompt()`.
|
|
110
|
-
핵심 동작: Bash(tfx-route.sh) → SendMessage(보고) → 피드백 대기 → 재실행(N회) → TaskUpdate(completed) → 종료.
|
|
111
|
-
|
|
112
|
-
**핵심 규칙 요약:**
|
|
113
|
-
- Agent 래퍼 생략 금지 — 단일 워커도 반드시 Agent로 spawn (네비게이션 등록)
|
|
114
|
-
- `mode: "bypassPermissions"` 필수 — 모든 Agent에 포함
|
|
115
|
-
- `tfx-route.sh` 경유 필수 — 직접 `codex exec`/`gemini -y -p` 호출 금지
|
|
116
|
-
- 코드 직접 조작 금지 — 워커가 Read/Edit/Write 등 도구 직접 사용 금지
|
|
117
|
-
|
|
118
|
-
> 래퍼 규칙의 상세 이유와 인터럽트 프로토콜 → [`references/agent-wrapper-rules.md`](references/agent-wrapper-rules.md) 참조.
|
|
119
|
-
|
|
120
|
-
#### Step 3d: claude 타입만 Agent 직접 실행
|
|
121
|
-
|
|
122
|
-
```
|
|
123
|
-
Agent({
|
|
124
|
-
name: "claude-worker-{n}", team_name: teamName, mode: "bypassPermissions",
|
|
125
|
-
run_in_background: true, subagent_type: "{role}",
|
|
126
|
-
prompt: "TaskGet → TaskUpdate(in_progress) → 작업 수행 → TaskUpdate(completed, metadata: {result}) + SendMessage(to: team-lead)"
|
|
127
|
-
})
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
status는 "completed"만 사용. 실패 여부는 `metadata.result`로 구분.
|
|
131
|
-
|
|
132
|
-
#### Step 3e: 사용자 안내
|
|
133
|
-
|
|
134
|
-
"팀 생성 완료. Shift+Down으로 워커 전환, Shift+Tab으로 이전 워커."
|
|
135
|
-
|
|
136
|
-
### Phase 4: 결과 수집
|
|
137
|
-
|
|
138
|
-
`team_task_list`가 최종 truth source: `Bash("node hub/bridge.mjs team-task-list --team ${teamName}")`
|
|
139
|
-
- `metadata.result == "failed"` → Claude fallback 재시도
|
|
140
|
-
- Hub `task-update`에서는 `"failed"` 사용 가능. Claude Code `TaskUpdate`만 `"completed"` + `metadata.result`로 구분.
|
|
141
|
-
|
|
142
|
-
### Phase 5: 정리 (반드시 실행)
|
|
143
|
-
|
|
144
|
-
성공/실패에 관계없이 반드시 실행. TeamDelete를 건너뛰면 `~/.claude/teams/{teamName}/`이 잔존하여 무한 루프 발생.
|
|
145
|
-
|
|
146
|
-
1. 백그라운드 Agent 완료를 **최대 30초** 대기
|
|
147
|
-
2. `TeamDelete()` 호출
|
|
148
|
-
3. 실패 시 `forceCleanupTeam(teamName)` → 그래도 실패 시 `rm -rf ~/.claude/teams/{teamName}/` 안내
|
|
149
|
-
4. 종합 보고서 출력
|
|
150
|
-
|
|
151
|
-
### Phase 3-direct: Lead-Direct Headless 실행 (v6.0.0, 기본)
|
|
76
|
+
### Phase 3: Lead-Direct Headless 실행 (v6.0.0, 기본)
|
|
152
77
|
|
|
153
78
|
CLI 워커(Codex/Gemini/Claude)를 Agent 래퍼 없이 Lead가 headless.mjs로 직접 실행.
|
|
154
79
|
Windows Terminal에 psmux 세션이 자동 팝업되어 사용자가 실시간으로 CLI 출력을 확인.
|
|
@@ -201,6 +126,18 @@ Phase 3 선택:
|
|
|
201
126
|
**시각적 확인:** Windows Terminal 자동 팝업 + pane 타이틀 `codex (reviewer)` + triflux 테마.
|
|
202
127
|
**실수로 닫아도:** psmux 세션은 독립적. `psmux attach -t 세션이름`으로 재연결.
|
|
203
128
|
|
|
129
|
+
### Phase 4: 결과 수집 + 정리
|
|
130
|
+
|
|
131
|
+
headless 완료 후 `handle.results`에서 직접 수집. `handle.kill()`로 세션 정리.
|
|
132
|
+
실패 워커(`exitCode !== 0`)는 Claude fallback 재시도.
|
|
133
|
+
|
|
134
|
+
### Phase 3-fallback: Native Teams (psmux 미설치 시)
|
|
135
|
+
|
|
136
|
+
psmux가 없는 환경에서만 사용. Agent slim wrapper로 CLI를 실행.
|
|
137
|
+
`hub/team/native.mjs`의 `buildSlimWrapperPrompt()` 기반.
|
|
138
|
+
|
|
139
|
+
> 래퍼 규칙 상세 → [`references/agent-wrapper-rules.md`](references/agent-wrapper-rules.md)
|
|
140
|
+
|
|
204
141
|
**레거시 인터랙티브 모드:** `Bash("node {PKG_ROOT}/bin/triflux.mjs multi --no-attach --agents {agents} \\\"{task}\\\"")`
|
|
205
142
|
|
|
206
143
|
## 전제 조건
|