triflux 3.2.0-dev.2 → 3.2.0-dev.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.
@@ -47,10 +47,10 @@ function atomicWriteJson(path, value) {
47
47
  renameSync(tmp, path);
48
48
  }
49
49
 
50
- function sleepMs(ms) {
51
- const end = Date.now() + ms;
52
- while (Date.now() < end) {}
53
- }
50
+ function sleepMs(ms) {
51
+ // busy-wait를 피하고 Atomics.wait로 동기 대기 (CPU 점유 최소화)
52
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
53
+ }
54
54
 
55
55
  function withFileLock(lockPath, fn, retries = 20, delayMs = 25) {
56
56
  let fd = null;
@@ -419,37 +419,42 @@ export function teamSendMessage(args = {}) {
419
419
  return err('TEAM_NOT_FOUND', `팀 디렉토리가 없습니다: ${paths.team_dir}`);
420
420
  }
421
421
 
422
- const recipient = sanitizeRecipientName(to);
423
- const inboxFile = join(paths.inboxes_dir, `${recipient}.json`);
424
- const queue = readJsonSafe(inboxFile);
425
- const list = Array.isArray(queue) ? queue : [];
426
-
427
- const message = {
428
- id: randomUUID(),
429
- from: String(from),
430
- text: String(text),
431
- ...(summary ? { summary: String(summary) } : {}),
432
- timestamp: new Date().toISOString(),
433
- color: String(color || 'blue'),
434
- read: false,
435
- };
436
- list.push(message);
437
-
438
- try {
439
- atomicWriteJson(inboxFile, list);
440
- } catch (e) {
441
- return err('SEND_MESSAGE_FAILED', e.message);
442
- }
443
-
444
- const unreadCount = list.filter((m) => m?.read !== true).length;
445
- return {
446
- ok: true,
447
- data: {
448
- message_id: message.id,
449
- recipient,
450
- inbox_file: inboxFile,
451
- queued_at: message.timestamp,
452
- unread_count: unreadCount,
453
- },
454
- };
455
- }
422
+ const recipient = sanitizeRecipientName(to);
423
+ const inboxFile = join(paths.inboxes_dir, `${recipient}.json`);
424
+ const lockFile = `${inboxFile}.lock`;
425
+ let message;
426
+
427
+ try {
428
+ const unreadCount = withFileLock(lockFile, () => {
429
+ const queue = readJsonSafe(inboxFile);
430
+ const list = Array.isArray(queue) ? queue : [];
431
+
432
+ message = {
433
+ id: randomUUID(),
434
+ from: String(from),
435
+ text: String(text),
436
+ ...(summary ? { summary: String(summary) } : {}),
437
+ timestamp: new Date().toISOString(),
438
+ color: String(color || 'blue'),
439
+ read: false,
440
+ };
441
+ list.push(message);
442
+ atomicWriteJson(inboxFile, list);
443
+
444
+ return list.filter((m) => m?.read !== true).length;
445
+ });
446
+
447
+ return {
448
+ ok: true,
449
+ data: {
450
+ message_id: message.id,
451
+ recipient,
452
+ inbox_file: inboxFile,
453
+ queued_at: message.timestamp,
454
+ unread_count: unreadCount,
455
+ },
456
+ };
457
+ } catch (e) {
458
+ return err('SEND_MESSAGE_FAILED', e.message);
459
+ }
460
+ }
package/hub/team/pane.mjs CHANGED
@@ -39,13 +39,18 @@ function tmux(args, opts = {}) {
39
39
  /**
40
40
  * CLI 에이전트 시작 커맨드 생성
41
41
  * @param {'codex'|'gemini'|'claude'} cli
42
- * @returns {string} 실행할 커맨드
43
- */
44
- export function buildCliCommand(cli) {
45
- switch (cli) {
46
- case "codex":
47
- // interactive REPL 진입 — MCP는 ~/.codex/config.json에 사전 등록
48
- return "codex";
42
+ * @param {{ trustMode?: boolean }} [options]
43
+ * @returns {string} 실행할 셸 커맨드
44
+ */
45
+ export function buildCliCommand(cli, options = {}) {
46
+ const { trustMode = false } = options;
47
+
48
+ switch (cli) {
49
+ case "codex":
50
+ // trust 모드에서는 승인/샌드박스 우회 + alt-screen 비활성화
51
+ return trustMode
52
+ ? "codex --dangerously-bypass-approvals-and-sandbox --no-alt-screen"
53
+ : "codex";
49
54
  case "gemini":
50
55
  // interactive 모드 — MCP는 ~/.gemini/settings.json에 사전 등록
51
56
  return "gemini";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "triflux",
3
- "version": "3.2.0-dev.2",
3
+ "version": "3.2.0-dev.3",
4
4
  "description": "CLI-first multi-model orchestrator for Claude Code — route tasks to Codex, Gemini, and Claude",
5
5
  "type": "module",
6
6
  "bin": {
@@ -31,12 +31,19 @@ CONTEXT_FILE="${5:-}"
31
31
  CODEX_BIN="${CODEX_BIN:-$(command -v codex 2>/dev/null || echo codex)}"
32
32
  GEMINI_BIN="${GEMINI_BIN:-$(command -v gemini 2>/dev/null || echo gemini)}"
33
33
 
34
- # ── 상수 ──
35
- MAX_STDOUT_BYTES=51200 # 50KB — Claude 컨텍스트 절약
36
- TIMESTAMP=$(date +%s)
37
- STDERR_LOG="/tmp/tfx-route-${AGENT_TYPE}-${TIMESTAMP}-stderr.log"
38
- STDOUT_LOG="/tmp/tfx-route-${AGENT_TYPE}-${TIMESTAMP}-stdout.log"
39
- TFX_TMP="${TMPDIR:-/tmp}"
34
+ # ── 상수 ──
35
+ MAX_STDOUT_BYTES=51200 # 50KB — Claude 컨텍스트 절약
36
+ TIMESTAMP=$(date +%s)
37
+ STDERR_LOG="/tmp/tfx-route-${AGENT_TYPE}-${TIMESTAMP}-stderr.log"
38
+ STDOUT_LOG="/tmp/tfx-route-${AGENT_TYPE}-${TIMESTAMP}-stdout.log"
39
+ TFX_TMP="${TMPDIR:-/tmp}"
40
+
41
+ # ── 팀 환경변수 ──
42
+ TFX_TEAM_NAME="${TFX_TEAM_NAME:-}"
43
+ TFX_TEAM_TASK_ID="${TFX_TEAM_TASK_ID:-}"
44
+ TFX_TEAM_AGENT_NAME="${TFX_TEAM_AGENT_NAME:-${AGENT_TYPE}-worker-$$}"
45
+ TFX_TEAM_LEAD_NAME="${TFX_TEAM_LEAD_NAME:-team-lead}"
46
+ TFX_HUB_URL="${TFX_HUB_URL:-http://127.0.0.1:27888}"
40
47
 
41
48
  # fallback 시 원래 에이전트 정보 보존
42
49
  ORIGINAL_AGENT=""
@@ -49,13 +56,83 @@ register_agent() {
49
56
  > "$agent_file" 2>/dev/null || true
50
57
  }
51
58
 
52
- deregister_agent() {
53
- rm -f "${TFX_TMP}/tfx-agent-$$.json" 2>/dev/null || true
54
- }
55
-
56
- # ── 라우팅 테이블 ──
57
- # 반환: CLI_CMD, CLI_ARGS, CLI_TYPE, CLI_EFFORT, DEFAULT_TIMEOUT, RUN_MODE, OPUS_OVERSIGHT
58
- route_agent() {
59
+ deregister_agent() {
60
+ rm -f "${TFX_TMP}/tfx-agent-$$.json" 2>/dev/null || true
61
+ }
62
+
63
+ # ── Hub Bridge 통신 ──
64
+ # JSON 문자열 이스케이프 (큰따옴표, 백슬래시, 개행, 탭, CR)
65
+ json_escape() {
66
+ local s="${1:-}"
67
+ s="${s//\\/\\\\}"
68
+ s="${s//\"/\\\"}"
69
+ s="${s//$'\n'/\\n}"
70
+ s="${s//$'\t'/\\t}"
71
+ s="${s//$'\r'/\\r}"
72
+ echo "$s"
73
+ }
74
+
75
+ team_claim_task() {
76
+ [[ -z "$TFX_TEAM_NAME" || -z "$TFX_TEAM_TASK_ID" ]] && return 0
77
+ local http_code safe_team_name safe_task_id safe_agent_name
78
+ safe_team_name=$(json_escape "$TFX_TEAM_NAME")
79
+ safe_task_id=$(json_escape "$TFX_TEAM_TASK_ID")
80
+ safe_agent_name=$(json_escape "$TFX_TEAM_AGENT_NAME")
81
+
82
+ http_code=$(curl -sf -o /dev/null -w "%{http_code}" -X POST "${TFX_HUB_URL}/bridge/team/task-update" \
83
+ -H "Content-Type: application/json" \
84
+ -d "{\"team_name\":\"${safe_team_name}\",\"task_id\":\"${safe_task_id}\",\"claim\":true,\"owner\":\"${safe_agent_name}\",\"status\":\"in_progress\"}" \
85
+ 2>/dev/null) || http_code="000"
86
+
87
+ case "$http_code" in
88
+ 200) ;; # 성공
89
+ 409)
90
+ echo "[tfx-route] CLAIM_CONFLICT: task ${TFX_TEAM_TASK_ID}가 이미 claim됨. 실행 중단." >&2
91
+ exit 0 ;;
92
+ 000)
93
+ echo "[tfx-route] 경고: Hub 연결 실패 (미실행?). claim 없이 계속 실행." >&2 ;;
94
+ *)
95
+ echo "[tfx-route] 경고: Hub claim 응답 HTTP ${http_code}. claim 없이 계속 실행." >&2 ;;
96
+ esac
97
+ }
98
+
99
+ team_complete_task() {
100
+ local result_status="${1:-completed}"
101
+ local result_summary="${2:-작업 완료}"
102
+ local safe_team_name safe_task_id safe_agent_name safe_status
103
+ [[ -z "$TFX_TEAM_NAME" || -z "$TFX_TEAM_TASK_ID" ]] && return 0
104
+ safe_team_name=$(json_escape "$TFX_TEAM_NAME")
105
+ safe_task_id=$(json_escape "$TFX_TEAM_TASK_ID")
106
+ safe_agent_name=$(json_escape "$TFX_TEAM_AGENT_NAME")
107
+ safe_status=$(json_escape "$result_status")
108
+
109
+ # task 상태 업데이트
110
+ curl -sf -X POST "${TFX_HUB_URL}/bridge/team/task-update" \
111
+ -H "Content-Type: application/json" \
112
+ -d "{\"team_name\":\"${safe_team_name}\",\"task_id\":\"${safe_task_id}\",\"status\":\"${safe_status}\",\"owner\":\"${safe_agent_name}\"}" \
113
+ >/dev/null 2>&1 || true
114
+
115
+ # 리드에게 메시지 전송
116
+ local msg_text safe_text safe_lead_name
117
+ msg_text=$(echo "$result_summary" | head -c 4096)
118
+ safe_text=$(json_escape "$msg_text")
119
+ safe_lead_name=$(json_escape "$TFX_TEAM_LEAD_NAME")
120
+
121
+ curl -sf -X POST "${TFX_HUB_URL}/bridge/team/send-message" \
122
+ -H "Content-Type: application/json" \
123
+ -d "{\"team_name\":\"${safe_team_name}\",\"from\":\"${safe_agent_name}\",\"to\":\"${safe_lead_name}\",\"text\":\"${safe_text}\",\"summary\":\"task ${safe_task_id} ${safe_status}\"}" \
124
+ >/dev/null 2>&1 || true
125
+
126
+ # Hub result 발행 (poll_messages 채널 활성화)
127
+ curl -sf -X POST "${TFX_HUB_URL}/bridge/result" \
128
+ -H "Content-Type: application/json" \
129
+ -d "{\"agent_id\":\"${safe_agent_name}\",\"topic\":\"task.result\",\"payload\":{\"task_id\":\"${safe_task_id}\",\"status\":\"${safe_status}\"},\"trace_id\":\"${safe_team_name}\"}" \
130
+ >/dev/null 2>&1 || true
131
+ }
132
+
133
+ # ── 라우팅 테이블 ──
134
+ # 반환: CLI_CMD, CLI_ARGS, CLI_TYPE, CLI_EFFORT, DEFAULT_TIMEOUT, RUN_MODE, OPUS_OVERSIGHT
135
+ route_agent() {
59
136
  local agent="$1"
60
137
  local codex_base="--dangerously-bypass-approvals-and-sandbox --skip-git-repo-check"
61
138
 
@@ -351,16 +428,20 @@ ${ctx_content}
351
428
  local FULL_PROMPT="$PROMPT"
352
429
  [[ -n "$mcp_hint" ]] && FULL_PROMPT="${PROMPT}. ${mcp_hint}"
353
430
 
354
- # 메타정보 (stderr)
355
- echo "[tfx-route] v${VERSION} type=$CLI_TYPE agent=$AGENT_TYPE effort=$CLI_EFFORT mode=$RUN_MODE timeout=${TIMEOUT_SEC}s" >&2
356
- echo "[tfx-route] opus_oversight=$OPUS_OVERSIGHT mcp_profile=$MCP_PROFILE" >&2
357
-
358
- # Per-process 에이전트 등록
359
- register_agent
360
-
361
- # CLI 실행 (stderr 분리 + 타임아웃 + 소요시간 측정)
362
- local exit_code=0
363
- local start_time
431
+ # 메타정보 (stderr)
432
+ echo "[tfx-route] v${VERSION} type=$CLI_TYPE agent=$AGENT_TYPE effort=$CLI_EFFORT mode=$RUN_MODE timeout=${TIMEOUT_SEC}s" >&2
433
+ echo "[tfx-route] opus_oversight=$OPUS_OVERSIGHT mcp_profile=$MCP_PROFILE" >&2
434
+ [[ -n "$TFX_TEAM_NAME" ]] && echo "[tfx-route] team=$TFX_TEAM_NAME task=$TFX_TEAM_TASK_ID agent=$TFX_TEAM_AGENT_NAME" >&2
435
+
436
+ # Per-process 에이전트 등록
437
+ register_agent
438
+
439
+ # 팀 모드: task claim
440
+ team_claim_task
441
+
442
+ # CLI 실행 (stderr 분리 + 타임아웃 + 소요시간 측정)
443
+ local exit_code=0
444
+ local start_time
364
445
  start_time=$(date +%s)
365
446
 
366
447
  if [[ "$CLI_TYPE" == "codex" ]]; then
@@ -410,13 +491,28 @@ ${ctx_content}
410
491
  fi
411
492
  fi
412
493
 
413
- local end_time
414
- end_time=$(date +%s)
415
- local elapsed=$((end_time - start_time))
416
-
417
- # ── 후처리: 단일 node 프로세스로 위임 ──
418
- # 토큰 추출, 출력 필터링, 로그, 토큰 누적, AIMD, 이슈 추적, 결과 출력 전부 처리
419
- local post_script="${HOME}/.claude/scripts/tfx-route-post.mjs"
494
+ local end_time
495
+ end_time=$(date +%s)
496
+ local elapsed=$((end_time - start_time))
497
+
498
+ # 모드: task complete + 리드 보고
499
+ if [[ -n "$TFX_TEAM_NAME" ]]; then
500
+ if [[ "$exit_code" -eq 0 ]]; then
501
+ local output_preview
502
+ output_preview=$(head -c 2048 "$STDOUT_LOG" 2>/dev/null || echo "출력 없음")
503
+ team_complete_task "completed" "$output_preview"
504
+ elif [[ "$exit_code" -eq 124 ]]; then
505
+ team_complete_task "failed" "타임아웃 (${TIMEOUT_SEC}초)"
506
+ else
507
+ local err_preview
508
+ err_preview=$(tail -c 1024 "$STDERR_LOG" 2>/dev/null || echo "에러 정보 없음")
509
+ team_complete_task "failed" "exit_code=${exit_code}: ${err_preview}"
510
+ fi
511
+ fi
512
+
513
+ # ── 후처리: 단일 node 프로세스로 위임 ──
514
+ # 토큰 추출, 출력 필터링, 로그, 토큰 누적, AIMD, 이슈 추적, 결과 출력 전부 처리
515
+ local post_script="${HOME}/.claude/scripts/tfx-route-post.mjs"
420
516
  if [[ -f "$post_script" ]]; then
421
517
  node "$post_script" \
422
518
  --agent "$AGENT_TYPE" \
@@ -1,47 +1,47 @@
1
- ---
2
- name: tfx-team
3
- description: 멀티-CLI 팀 모드. Claude Native Agent Teams + Codex/Gemini 멀티모델 오케스트레이션.
4
- triggers:
5
- - tfx-team
6
- argument-hint: '"작업 설명" | --agents codex,gemini "작업" | --tmux "작업" | status | stop'
7
- ---
8
-
9
- # tfx-team v2 — Claude Native Teams 기반 멀티-CLI 팀 오케스트레이터
10
-
11
- > Claude Code 네이티브 Agent Teams (in-process 모드)를 활용하여
12
- > tmux 없이 현재 터미널에서 Codex/Gemini/Claude 멀티모델 팀을 구성한다.
13
- > 각 teammate는 Claude Code 인스턴스이지만 Codex/Gemini CLI **래퍼**로 동작하여 토큰을 최소화한다.
14
-
15
- | | tfx-auto | tfx-team v1 (tmux) | **tfx-team v2 (native)** |
16
- |--|----------|--------------------|-----------------------------|
17
- | 트리아지 | Codex 분류 Opus 분해 | 동일 | **동일** |
18
- | 실행 | tfx-route.sh one-shot | tmux pane interactive | **Native Teams in-process** |
19
- | 관찰 | stdout 반환 | Ctrl+B 방향키 | **Shift+Down teammate 전환** |
20
- | 통신 | 없음 | Hub MCP 버스 | **내장 Mailbox + Task List** |
21
- | 개입 | 불가 | tfx team send | **SendMessage** |
22
- | tmux 필요 | | | **✗** |
23
-
24
- ## 사용법
25
-
1
+ ---
2
+ name: tfx-team
3
+ description: 멀티-CLI 팀 모드. Claude Native Agent Teams + Codex/Gemini 멀티모델 오케스트레이션.
4
+ triggers:
5
+ - tfx-team
6
+ argument-hint: '"작업 설명" | --agents codex,gemini "작업" | --tmux "작업" | status | stop'
7
+ ---
8
+
9
+ # tfx-team v2.1Lead Direct Bash 기반 멀티-CLI 팀 오케스트레이터
10
+
11
+ > Claude Code Native Teams 유지하되, Codex/Gemini 실행 경로에서 Claude teammate 래퍼를 제거한다.
12
+ > 리드가 `tfx-route.sh`를 직접 병렬 실행하고, task 상태는 `team_task_list`를 truth source로 검증한다.
13
+
14
+ | 구분 | v2 (기존) | v2.1 (현재) |
15
+ |--|--|--|
16
+ | 실행 | `Agent(teammate)` → `Bash(tfx-route.sh)` | `Lead` → `Bash(tfx-route.sh)` 직접 |
17
+ | teammate | Claude Opus 인스턴스 × N | Codex/Gemini용 없음 |
18
+ | task claim | teammate가 `TaskUpdate` 호출 | `tfx-route.sh`가 Hub bridge로 claim |
19
+ | 결과 보고 | teammate가 `SendMessage` 호출 | `tfx-route.sh`가 Hub bridge로 `send-message` 호출 |
20
+ | 결과 수집 | SendMessage 자동 수신 중심 | `team_task_list` 폴링 + stdout/결과 로그 |
21
+ | 정리 | `shutdown_request` × N `TeamDelete` | `TeamDelete` 직접 |
22
+ | Opus 토큰 | N × 래퍼 오버헤드 | 래퍼 오버헤드 0 |
23
+
24
+ ## 사용법
25
+
26
26
  ```
27
27
  /tfx-team "인증 리팩터링 + UI 개선 + 보안 리뷰"
28
28
  /tfx-team --agents codex,gemini "프론트+백엔드"
29
29
  /tfx-team --tmux "작업" # 레거시 tmux 모드
30
30
  /tfx-team status
31
- /tfx-team stop
32
- ```
33
-
34
- ## 실행 워크플로우
35
-
36
- ### Phase 1: 입력 파싱
37
-
38
- ```
39
- 입력: "3:codex 리뷰" → 수동 모드: N=3, agent=codex
40
- 입력: "인증 + UI + 테스트" → 자동 모드: Codex 분류 → Opus 분해
41
- 입력: "--tmux 인증 + UI" → tmux 레거시 모드 → Phase 3-tmux로 분기
42
- 입력: "status" → 제어 커맨드
43
- 입력: "stop" → 제어 커맨드
44
- ```
31
+ /tfx-team stop
32
+ ```
33
+
34
+ ## 실행 워크플로우
35
+
36
+ ### Phase 1: 입력 파싱
37
+
38
+ ```
39
+ 입력: "3:codex 리뷰" → 수동 모드: N=3, agent=codex
40
+ 입력: "인증 + UI + 테스트" → 자동 모드: Codex 분류 → Opus 분해
41
+ 입력: "--tmux 인증 + UI" → tmux 레거시 모드 → Phase 3-tmux로 분기
42
+ 입력: "status" → 제어 커맨드
43
+ 입력: "stop" → 제어 커맨드
44
+ ```
45
45
 
46
46
  **제어 커맨드 감지:**
47
47
  - `status`, `stop`, `kill`, `attach`, `list`, `send` → `Bash("node bin/triflux.mjs team {cmd}")` 직행
@@ -49,26 +49,26 @@ argument-hint: '"작업 설명" | --agents codex,gemini "작업" | --tmux "작
49
49
  - 그 외 → Phase 2 트리아지
50
50
 
51
51
  **--tmux 감지:** 입력에 `--tmux`가 포함되면 Phase 3-tmux로 분기.
52
-
53
- ### Phase 2: 트리아지 (tfx-auto와 동일)
54
-
55
- #### 자동 모드 — Codex 분류 → Opus 분해
56
-
57
- ```bash
58
- # Step 2a: Codex 분류 (무료)
59
- Bash("codex exec --full-auto --skip-git-repo-check '다음 작업을 분석하고 각 부분에 적합한 agent를 분류하라.
60
-
61
- agent 선택:
62
- - codex: 코드 구현/수정/분석/리뷰/디버깅/설계 (기본값)
63
- - gemini: 문서/UI/디자인/멀티모달
64
- - claude: 코드베이스 탐색/테스트 실행/검증 (최후 수단)
65
-
66
- 작업: {task}
67
-
68
- JSON만 출력:
69
- { \"parts\": [{ \"description\": \"...\", \"agent\": \"codex|gemini|claude\" }] }
70
- '")
71
- ```
52
+
53
+ ### Phase 2: 트리아지 (tfx-auto와 동일)
54
+
55
+ #### 자동 모드 — Codex 분류 → Opus 분해
56
+
57
+ ```bash
58
+ # Step 2a: Codex 분류 (무료)
59
+ Bash("codex exec --full-auto --skip-git-repo-check '다음 작업을 분석하고 각 부분에 적합한 agent를 분류하라.
60
+
61
+ agent 선택:
62
+ - codex: 코드 구현/수정/분석/리뷰/디버깅/설계 (기본값)
63
+ - gemini: 문서/UI/디자인/멀티모달
64
+ - claude: 코드베이스 탐색/테스트 실행/검증 (최후 수단)
65
+
66
+ 작업: {task}
67
+
68
+ JSON만 출력:
69
+ { \"parts\": [{ \"description\": \"...\", \"agent\": \"codex|gemini|claude\" }] }
70
+ '")
71
+ ```
72
72
 
73
73
  > Codex 분류 실패 시 → Opus(오케스트레이터)가 직접 분류+분해
74
74
 
@@ -79,161 +79,129 @@ Bash("codex exec --full-auto --skip-git-repo-check '다음 작업을 분석하
79
79
  { cli: "gemini", subtask: "UI 개선", role: "designer" },
80
80
  { cli: "codex", subtask: "보안 리뷰", role: "reviewer" }]
81
81
  ```
82
-
83
- #### 수동 모드 (`N:agent_type` 또는 `--agents`)
84
-
85
- Codex 분류 건너뜀 → Opus가 직접 N개 서브태스크 분해.
86
-
87
- ### Phase 3: Native Teams 실행
88
-
89
- 트리아지 결과를 Claude Code 네이티브 Agent Teams로 실행한다.
90
-
91
- #### Step 3a: 팀 생성
92
-
82
+
83
+ #### 수동 모드 (`N:agent_type` 또는 `--agents`)
84
+
85
+ Codex 분류 건너뜀 → Opus가 직접 N개 서브태스크 분해.
86
+
87
+ ### Phase 3: Native Teams 실행 (v2.1 개편)
88
+
89
+ 트리아지 결과를 Claude Code 네이티브 Agent Teams로 실행한다.
90
+
91
+ #### Step 3a: 팀 생성
92
+
93
93
  ```
94
94
  teamName = "tfx-" + Date.now().toString(36).slice(-6)
95
95
 
96
96
  TeamCreate({
97
97
  team_name: teamName,
98
98
  description: "tfx-team: {원본 작업 요약}"
99
- })
100
- ```
101
-
102
- #### Step 3b: 공유 작업 등록
103
-
104
- 각 서브태스크에 대해 TaskCreate 호출한다:
105
-
106
- ```
107
- for each assignment in assignments:
108
- TaskCreate({
109
- subject: assignment.subtask,
110
- description: "CLI: {assignment.cli}, 역할: {assignment.role}\n\n{상세 작업 내용}",
111
- metadata: { cli: assignment.cli, role: assignment.role }
112
- })
113
- ```
114
-
115
- #### Step 3c: Teammate 스폰
116
-
117
- **Agent 도구**에 `team_name`과 `name` 파라미터를 전달하여 teammate를 생성한다.
118
- **모든 teammate를 `run_in_background: true`로 병렬 스폰한다.**
119
-
120
- 각 CLI 타입별 teammate 프롬프트:
121
-
122
- **codex-worker (Codex CLI 래퍼):**
123
-
124
- ```
125
- Agent({
126
- name: "codex-worker-{n}",
127
- team_name: teamName,
128
- description: "codex-worker-{n}",
129
- run_in_background: true,
130
- prompt: "너는 {teamName}의 Codex 워커이다.
131
-
132
- [실행 규칙]
133
- 1. TaskList를 호출하여 너에게 배정된 pending 작업을 확인하라
134
- 2. TaskGet으로 작업 상세를 읽고, TaskUpdate(status: in_progress, owner: 너의 이름)로 claim
135
- 3. Bash(\"bash ~/.claude/scripts/tfx-route.sh {role} '{subtask}' auto\")로 실행
136
- 4. 결과를 확인하고:
137
- - 성공: TaskUpdate(status: completed) + SendMessage(type: message, recipient: team-lead, summary: '작업 완료')
138
- - 실패: TaskUpdate에 에러 기록 + SendMessage로 리드에게 보고
139
- 5. TaskList를 다시 확인하고 추가 pending 작업이 있으면 반복
140
-
141
- [규칙]
142
- - 실제 구현은 Codex CLI가 수행 — 너는 실행+보고 역할만
143
- - tfx-route.sh 결과를 그대로 보고하라 (요약하지 것)"
144
- })
145
- ```
146
-
147
- **gemini-worker (Gemini CLI 래퍼):**
148
-
149
- ```
150
- Agent({
151
- name: "gemini-worker-{n}",
152
- team_name: teamName,
153
- description: "gemini-worker-{n}",
154
- run_in_background: true,
155
- prompt: "너는 {teamName}의 Gemini 워커이다.
156
-
157
- [실행 규칙]
158
- 1. TaskList를 호출하여 너에게 배정된 pending 작업을 확인하라
159
- 2. TaskGet으로 작업 상세를 읽고, TaskUpdate(status: in_progress, owner: 너의 이름)로 claim
160
- 3. Bash(\"bash ~/.claude/scripts/tfx-route.sh {role} '{subtask}' auto\")로 실행
161
- 4. 결과를 확인하고:
162
- - 성공: TaskUpdate(status: completed) + SendMessage(type: message, recipient: team-lead, summary: '작업 완료')
163
- - 실패: TaskUpdate에 에러 기록 + SendMessage로 리드에게 보고
164
- 5. TaskList를 다시 확인하고 추가 pending 작업이 있으면 반복
165
-
166
- [규칙]
167
- - 실제 구현은 Gemini CLI가 수행 너는 실행+보고 역할만
168
- - tfx-route.sh 결과를 그대로 보고하라 (요약하지 말 것)"
169
- })
170
- ```
171
-
172
- **claude-worker (직접 실행):**
173
-
174
- ```
175
- Agent({
176
- name: "claude-worker-{n}",
177
- team_name: teamName,
178
- description: "claude-worker-{n}",
179
- run_in_background: true,
180
- prompt: "너는 {teamName}의 Claude 워커이다.
181
-
182
- [실행 규칙]
183
- 1. TaskList를 호출하여 너에게 배정된 pending 작업을 확인하라
184
- 2. TaskGet으로 작업 상세를 읽고, TaskUpdate(status: in_progress, owner: 너의 이름)로 claim
185
- 3. Glob, Grep, Read, Bash 등 도구를 직접 사용하여 작업 수행
186
- 4. 완료 시 TaskUpdate(status: completed) + SendMessage(type: message, recipient: team-lead, summary: '작업 완료')
187
- 5. TaskList를 다시 확인하고 추가 pending 작업이 있으면 반복
188
-
189
- 에러 시 TaskUpdate + SendMessage로 리드에게 보고."
190
- })
191
- ```
192
-
193
- #### Step 3d: 사용자 안내
194
-
195
- ```
196
- "팀 '{teamName}' 생성 완료 ({N}명의 teammate).
197
- Shift+Down으로 teammate의 진행 상황을 확인할 수 있습니다."
198
- ```
199
-
200
- ### Phase 4: 결과 수집
201
-
202
- 리드(이 오케스트레이터)가:
203
- 1. teammate들의 SendMessage를 자동 수신 (폴링 불필요 — 자동 배달됨)
204
- 2. 모든 teammate가 결과를 보고하면 TaskList로 전체 상태 확인
205
- 3. 모든 작업이 completed이면 종합 보고서 출력:
206
-
207
- ```markdown
208
- ## tfx-team 실행 결과
209
-
210
- | # | Teammate | CLI | 작업 | 상태 |
211
- |---|----------|-----|------|------|
212
- | 1 | codex-worker-1 | codex | 인증 리팩터링 | ✓ completed |
213
- | 2 | gemini-worker-1 | gemini | UI 개선 | ✓ completed |
214
- | 3 | codex-worker-2 | codex | 보안 리뷰 | ✓ completed |
215
- ```
216
-
217
- ### Phase 5: 정리
218
-
219
- ```
220
- # 각 teammate에 shutdown 요청
221
- for each teammate in teammates:
222
- SendMessage({
223
- type: "shutdown_request",
224
- recipient: teammate.name,
225
- content: "모든 작업 완료. 종료합니다."
226
- })
227
-
228
- # 모든 teammate 종료 후 팀 삭제
229
- TeamDelete()
230
- ```
231
-
232
- > **중요:** TeamDelete는 활성 멤버가 있으면 실패한다. 반드시 모든 teammate에 shutdown_request를 보내고 종료를 확인한 후 호출.
233
-
234
- ### Phase 3-tmux: 레거시 tmux 모드
235
-
236
- `--tmux` 플래그가 있으면 기존 v1 방식으로 실행:
99
+ })
100
+ ```
101
+
102
+ #### Step 3b: 공유 작업 등록
103
+
104
+ 각 서브태스크에 대해 `TaskCreate`를 호출하고 `taskId`를 보존한다.
105
+ 리드가 실행 시 사용할 `agentName`도 함께 확정한다.
106
+
107
+ ```
108
+ for each assignment in assignments (index i):
109
+ TaskCreate({
110
+ subject: assignment.subtask,
111
+ description: "CLI: {assignment.cli}, 역할: {assignment.role}\n\n{상세 작업 내용}",
112
+ metadata: { cli: assignment.cli, role: assignment.role }
113
+ })
114
+ taskId = created_task.id
115
+ agentName = "{assignment.cli}-worker-{i+1}"
116
+ runQueue.push({ taskId, agentName, ...assignment })
117
+ ```
118
+
119
+ #### Step 3c: 리드가 직접 Bash 병렬 실행 (teammate 스폰 제거)
120
+
121
+ Codex/Gemini 서브태스크는 teammate를 만들지 않고, 리드가 직접 `Bash(..., run_in_background=true)`를 병렬 호출한다.
122
+
123
+ ```
124
+ for each item in runQueue where item.cli in ["codex", "gemini"]:
125
+ Bash(
126
+ "TFX_TEAM_NAME={teamName} TFX_TEAM_TASK_ID={item.taskId} TFX_TEAM_AGENT_NAME={item.agentName} TFX_TEAM_LEAD_NAME=team-lead bash ~/.claude/scripts/tfx-route.sh {item.role} \"{item.subtask}\" {mcp_profile}",
127
+ run_in_background=true
128
+ )
129
+ ```
130
+
131
+ `tfx-route.sh` 팀 통합 동작(이미 구현됨, `TFX_TEAM_*` 기반):
132
+ - `TFX_TEAM_NAME`: 팀 식별자
133
+ - `TFX_TEAM_TASK_ID`: 작업 식별자
134
+ - `TFX_TEAM_AGENT_NAME`: 워커 표기 이름
135
+ - `TFX_TEAM_LEAD_NAME`: 리드 수신자 이름 (기본 `team-lead`)
136
+
137
+ Bridge 연동:
138
+ - 실행 시작 `POST /bridge/team/task-update`로 `claim + in_progress`
139
+ - 실행 종료 `POST /bridge/team/task-update`로 `completed|failed`
140
+ - 리드 보고 `POST /bridge/team/send-message`
141
+ - 이벤트 채널 `POST /bridge/result` (`topic=task.result`) 발행
142
+
143
+ #### Step 3d: claude 타입만 Agent 직접 실행
144
+
145
+ `cli == claude`인 서브태스크에만 `Agent(subagent_type)`를 사용한다.
146
+
147
+ ```
148
+ Agent({
149
+ name: "claude-worker-{n}",
150
+ team_name: teamName,
151
+ description: "claude-worker-{n}",
152
+ run_in_background: true,
153
+ subagent_type: "{role}",
154
+ prompt: "너는 {teamName}의 Claude 워커이다.
155
+
156
+ 1. TaskGet 후 TaskUpdate(status: in_progress, owner: 너의 이름)로 claim
157
+ 2. 도구를 직접 사용해 작업 수행
158
+ 3. 성공 TaskUpdate(status: completed) + SendMessage(to: team-lead)
159
+ 4. 실패 TaskUpdate(status: failed) + SendMessage(to: team-lead)"
160
+ })
161
+ ```
162
+
163
+ #### Step 3e: 사용자 안내
164
+
165
+ ```
166
+ "팀 '{teamName}' 생성 완료.
167
+ Codex/Gemini 작업은 리드가 백그라운드 Bash로 병렬 실행 중이며,
168
+ claude 작업만 teammate로 실행됩니다."
169
+ ```
170
+
171
+ ### Phase 4: 결과 수집 (truth source = team_task_list)
172
+
173
+ 1. 리드가 Step 3c에서 실행한 모든 백그라운드 Bash 프로세스 완료를 대기한다.
174
+ 2. `team_task_list`를 최종 truth source로 조회한다.
175
+
176
+ ```bash
177
+ Bash("curl -sf http://127.0.0.1:27888/bridge/team/task-list -H \"Content-Type: application/json\" -d \"{\\\"team_name\\\":\\\"${teamName}\\\"}\"")
178
+ ```
179
+
180
+ 3. 상태가 `failed`인 task가 있으면 Claude fallback으로 재시도한다.
181
+ 4. 재시도 후 다시 `team_task_list`를 조회해 최종 상태를 확정한다.
182
+ 5. `send-message`와 `/bridge/result(task.result)` 이벤트는 진행 관찰 채널로 사용하고, 최종 판정은 반드시 `team_task_list` 기준으로 한다.
183
+
184
+ 종합 보고서 예시:
185
+ ```markdown
186
+ ## tfx-team 실행 결과
187
+
188
+ | # | Worker | CLI | 작업 | 상태 |
189
+ |---|--------|-----|------|------|
190
+ | 1 | codex-worker-1 | codex | 인증 리팩터링 | completed |
191
+ | 2 | gemini-worker-1 | gemini | UI 개선 | completed |
192
+ | 3 | claude-worker-1 | claude | 실패 fallback 재시도 | completed |
193
+ ```
194
+
195
+ ### Phase 5: 정리 (간소화)
196
+
197
+ teammate 래퍼를 제거했으므로 `shutdown_request` 브로드캐스트를 사용하지 않는다.
198
+ 정리 단계는 `TeamDelete()` 직접 호출로 마무리한다.
199
+
200
+ > **중요:** claude 타입 백그라운드 Agent가 아직 실행 중이면 `TeamDelete`가 실패할 수 있다. 해당 작업 완료를 확인한 뒤 삭제한다.
201
+
202
+ ### Phase 3-tmux: 레거시 tmux 모드
203
+
204
+ `--tmux` 플래그가 있으면 기존 v1 방식으로 실행:
237
205
 
238
206
  ```bash
239
207
  # PKG_ROOT: triflux 패키지 루트 (Bash로 확인)
@@ -243,48 +211,49 @@ Bash("node {PKG_ROOT}/bin/triflux.mjs team --no-attach --agents {agents.join(','
243
211
  이후 Phase 4-5 대신 사용자에게 tmux 세션 안내:
244
212
 
245
213
  ```
246
- "tmux 세션이 생성되었습니다.
247
- tmux attach -t {sessionId} 세션 연결
248
- Ctrl+B → 방향키 pane 전환
249
- Ctrl+B → D 세션 분리
250
- Ctrl+B → Z pane 전체화면"
251
- ```
252
-
253
- ## 에이전트 매핑
254
-
255
- | 분류 결과 | CLI | teammate 역할 | 실행 방법 |
256
- |----------|-----|--------------|----------|
257
- | codex | codex | Codex CLI 래퍼 | `tfx-route.sh {role} '{task}' auto` via Bash |
258
- | gemini | gemini | Gemini CLI 래퍼 | `tfx-route.sh {role} '{task}' auto` via Bash |
259
- | claude | claude | 직접 실행 | Glob/Grep/Read/Bash 직접 사용 |
260
-
261
- > **핵심 아이디어:** Claude teammate = Codex/Gemini 실행 래퍼
262
- > - Native Teams 인프라 (in-process, Task List, Mailbox) 활용
263
- > - 실제 작업은 Codex/Gemini가 수행 (비용 최소화)
264
- > - teammate의 Claude 토큰은 "실행+보고" 래퍼 역할에만 소비
265
-
266
- ## 전제 조건
267
-
268
- - **CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1** — settings.json env에 설정 (`tfx setup`이 자동 설정)
269
- - **codex/gemini CLI**해당 에이전트 사용
270
- - **tfx setup** — tfx-route.sh 동기화 + AGENT_TEAMS 자동 설정 (사전 실행 권장)
271
-
272
- ## 에러 처리
273
-
274
- | 에러 | 처리 |
275
- |------|------|
276
- | TeamCreate 실패 / Agent Teams 비활성 | `--tmux` 폴백 (Phase 3-tmux로 전환) |
277
- | tfx-route.sh 없음 | `tfx setup` 실행 안내 |
278
- | CLI 미설치 (codex/gemini) | 해당 서브태스크를 claude 워커로 대체 |
279
- | Codex 분류 실패 | Opus 직접 분류+분해 |
280
- | teammate 실행 에러 | TaskUpdate(status: failed) + SendMessage로 리드에게 보고 |
281
- | teammate shutdown 거부 | 재시도 1회, 이후 사용자에게 보고 |
282
-
283
- ## 관련
284
-
285
- | 항목 | 설명 |
286
- |------|------|
287
- | `hub/team/native.mjs` | Native Teams 래퍼 (프롬프트 템플릿, 설정 빌더) |
288
- | `hub/team/cli.mjs` | tmux CLI (`--tmux` 레거시 모드) |
289
- | `tfx-auto` | one-shot 실행 오케스트레이터 (병행 유지) |
290
- | `tfx-hub` | MCP 메시지 버스 관리 (tmux 모드용) |
214
+ "tmux 세션이 생성되었습니다.
215
+ tmux attach -t {sessionId} 세션 연결
216
+ Ctrl+B → 방향키 pane 전환
217
+ Ctrl+B → D 세션 분리
218
+ Ctrl+B → Z pane 전체화면"
219
+ ```
220
+
221
+ ## 에이전트 매핑
222
+
223
+ | 분류 결과 | CLI | 역할 | 실행 방법 |
224
+ |----------|-----|------|----------|
225
+ | codex | codex | Lead 직접 라우팅 | `TFX_TEAM_* bash ~/.claude/scripts/tfx-route.sh {role} "{task}" {mcp_profile}` |
226
+ | gemini | gemini | Lead 직접 라우팅 | `TFX_TEAM_* bash ~/.claude/scripts/tfx-route.sh {role} "{task}" {mcp_profile}` |
227
+ | claude | claude | Claude 직접 실행 | `Agent(subagent_type={role})`로 직접 수행 |
228
+
229
+ > **핵심 아이디어:** Codex/Gemini 작업은 `tfx-route.sh`가 팀 상태 동기화까지 직접 수행한다.
230
+ > 리드는 오케스트레이션에 집중하고, Claude 토큰은 래퍼 오버헤드 없이 필요한 직접 실행(`claude` 타입)에만 사용한다.
231
+
232
+ ## 전제 조건
233
+
234
+ - **CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1** — settings.json env에 설정 (`tfx setup`이 자동 설정)
235
+ - **codex/gemini CLI** — 해당 에이전트 사용 시
236
+ - **tfx setup** — tfx-route.sh 동기화 + AGENT_TEAMS 자동 설정 (사전 실행 권장)
237
+ - **Hub bridge 활성 상태** 기본 `http://127.0.0.1:27888` (`/bridge/team/*`, `/bridge/result` 사용)
238
+
239
+ ## 에러 처리
240
+
241
+ | 에러 | 처리 |
242
+ |------|------|
243
+ | TeamCreate 실패 / Agent Teams 비활성 | `--tmux` 폴백 (Phase 3-tmux로 전환) |
244
+ | tfx-route.sh 없음 | `tfx setup` 실행 안내 |
245
+ | CLI 미설치 (codex/gemini) | 해당 서브태스크를 claude 워커로 대체 |
246
+ | Codex 분류 실패 | Opus 직접 분류+분해 |
247
+ | Bash 실행 실패 (Lead) | task를 `failed`로 마킹 후 Claude fallback 재시도 |
248
+ | `team_task_list` 조회 실패 | `/bridge/result`/stdout로 임시 관찰 bridge 복구 뒤 상태 재검증 |
249
+ | claude fallback 실패 | 실패 task 목록/원인 요약 사용자 승인 대기 |
250
+
251
+ ## 관련
252
+
253
+ | 항목 | 설명 |
254
+ |------|------|
255
+ | `scripts/tfx-route.sh` | 통합 라우터 (`TFX_TEAM_*`, task claim/complete, send-message, `/bridge/result`) |
256
+ | `hub/team/native.mjs` | Native Teams 래퍼 (프롬프트 템플릿, 팀 설정 빌더) |
257
+ | `hub/team/cli.mjs` | tmux CLI (`--tmux` 레거시 모드) |
258
+ | `tfx-auto` | one-shot 실행 오케스트레이터 (병행 유지) |
259
+ | `tfx-hub` | MCP 메시지 버스 관리 (tmux 모드용) |