triflux 4.2.8 → 4.2.9

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.
@@ -126,9 +126,10 @@ function getRouteTimeout(role, _mcpProfile) {
126
126
  }
127
127
 
128
128
  /**
129
- * v2.2 슬림 래퍼 프롬프트 생성
130
- * Agent spawn으로 네비게이션에 등록하되, 실제 작업은 tfx-route.sh가 수행.
131
- * 프롬프트 ~100 토큰 목표 (v2의 ~500 대비 80% 감소).
129
+ * v3 슬림 래퍼 프롬프트 생성 (async 모드)
130
+ * --async로 즉시 시작 --job-wait로 내부 폴링 → --job-result로 결과 수집.
131
+ * Claude Code Bash 도구 600초 제한을 우회하여 scientist(24분), scientist-deep(60분)
132
+ * 장시간 워커를 안정적으로 실행한다.
132
133
  *
133
134
  * @param {'codex'|'gemini'} cli — CLI 타입
134
135
  * @param {object} opts
@@ -141,7 +142,7 @@ function getRouteTimeout(role, _mcpProfile) {
141
142
  * @param {string} [opts.mcp_profile] — MCP 프로필
142
143
  * @param {number} [opts.workerIndex] — 검색 힌트 회전에 사용할 워커 인덱스(1-based)
143
144
  * @param {string} [opts.searchTool] — 전용 검색 도구 힌트(brave-search|tavily|exa)
144
- * @param {number} [opts.bashTimeout] — Bash timeout(ms). 미지정 시 role/profile 기반 자동 산출.
145
+ * @param {number} [opts.bashTimeout] — (deprecated, async에서는 무시됨)
145
146
  * @returns {string} 슬림 래퍼 프롬프트
146
147
  */
147
148
  export function buildSlimWrapperPrompt(cli, opts = {}) {
@@ -156,23 +157,25 @@ export function buildSlimWrapperPrompt(cli, opts = {}) {
156
157
  workerIndex,
157
158
  searchTool = "",
158
159
  pipelinePhase = "",
159
- bashTimeout,
160
160
  } = opts;
161
161
 
162
- // role/profile 기반 timeout 산출 (기본 timeout + 60초 여유, ms 변환)
163
- const bashTimeoutMs = bashTimeout ?? (getRouteTimeout(role, mcp_profile) + 60) * 1000;
164
-
165
- // 셸 이스케이프
162
+ const routeTimeoutSec = getRouteTimeout(role, mcp_profile);
166
163
  const escaped = subtask.replace(/'/g, "'\\''");
167
164
  const pipelineHint = pipelinePhase
168
165
  ? `\n파이프라인 단계: ${pipelinePhase}`
169
166
  : '';
170
167
  const routeEnvPrefix = buildRouteEnvPrefix(agentName, workerIndex, searchTool);
171
168
 
172
- return `실행 프로토콜 (subagent_type="${SLIM_WRAPPER_SUBAGENT_TYPE}"):
173
- 1. Bash(command, timeout: ${bashTimeoutMs}) 아래 명령 1회만 실행
174
- 2. Bash 종료 TaskUpdate + SendMessage로 Claude Code 태스크 동기화
175
- 3. 종료${pipelineHint}
169
+ // Bash 도구 timeout (모두 600초 이내)
170
+ const launchTimeoutMs = 15000; // Step 1: fork + job_id 반환
171
+ const waitTimeoutMs = 570000; // Step 2: 내부 폴링 (540초 대기 + 여유)
172
+ const resultTimeoutMs = 30000; // Step 3: 결과 읽기
173
+
174
+ return `실행 프로토콜 (subagent_type="${SLIM_WRAPPER_SUBAGENT_TYPE}", async):
175
+ 1. --async로 백그라운드 시작 → JOB_ID 수신
176
+ 2. --job-wait로 완료 대기 (내부 15초 간격 폴링, 최대 540초)
177
+ 3. "still_running"이면 Step 2 반복, "done"이면 --job-result로 결과 수집
178
+ 4. TaskUpdate + SendMessage → 종료${pipelineHint}
176
179
 
177
180
  [HARD CONSTRAINT] 허용 도구: Bash, TaskUpdate, TaskGet, TaskList, SendMessage만 사용한다.
178
181
  Read, Edit, Write, Grep, Glob, Agent, WebSearch, WebFetch 등 다른 모든 도구 사용을 금지한다.
@@ -182,21 +185,33 @@ Read, Edit, Write, Grep, Glob, Agent, WebSearch, WebFetch 등 다른 모든 도
182
185
  gemini/codex를 직접 호출하지 마라. 반드시 tfx-route.sh를 거쳐야 한다.
183
186
  프롬프트를 파일로 저장하지 마라. tfx-route.sh가 인자로 받는다.
184
187
 
185
- Step 1 — Bash 실행:
186
- Bash(command: 'TFX_TEAM_NAME="${teamName}" TFX_TEAM_TASK_ID="${taskId}" TFX_TEAM_AGENT_NAME="${agentName}" TFX_TEAM_LEAD_NAME="${leadName}"${routeEnvPrefix} bash ${ROUTE_SCRIPT} "${role}" '"'"'${escaped}'"'"' ${mcp_profile}', timeout: ${bashTimeoutMs})
188
+ Step 1 — Async 시작 (즉시 리턴, <1초):
189
+ Bash(command: 'TFX_TEAM_NAME="${teamName}" TFX_TEAM_TASK_ID="${taskId}" TFX_TEAM_AGENT_NAME="${agentName}" TFX_TEAM_LEAD_NAME="${leadName}"${routeEnvPrefix} bash ${ROUTE_SCRIPT} --async "${role}" '"'"'${escaped}'"'"' ${mcp_profile} ${routeTimeoutSec}', timeout: ${launchTimeoutMs})
190
+ → 출력 한 줄이 JOB_ID이다. 반드시 기억하라.
191
+
192
+ Step 2 — 완료 대기 (내부 폴링, 최대 540초):
193
+ Bash(command: 'bash ${ROUTE_SCRIPT} --job-wait JOB_ID 540', timeout: ${waitTimeoutMs})
194
+ → 주기적 "waiting elapsed=Ns progress=NB" 출력 후 최종 상태:
195
+ "done" → Step 3으로
196
+ "timeout" 또는 "failed ..." → Step 4로 (실패 보고)
197
+ "still_running ..." → Step 2 반복 (같은 명령 재실행)
198
+
199
+ Step 3 — 결과 수집:
200
+ Bash(command: 'bash ${ROUTE_SCRIPT} --job-result JOB_ID', timeout: ${resultTimeoutMs})
201
+ → 출력이 워커 실행 결과이다.
187
202
 
188
- Step 2 — Claude Code 태스크 동기화 (Bash 완료 후 반드시 실행):
189
- exit_code=0이면:
203
+ Step 4 — Claude Code 태스크 동기화 (반드시 실행):
204
+ "done"이면:
190
205
  TaskUpdate(taskId: "${taskId}", status: "completed", metadata: {result: "success"})
191
206
  SendMessage(type: "message", recipient: "${leadName}", content: "완료: ${agentName}", summary: "task ${taskId} success")
192
- exit_code≠0이면:
193
- TaskUpdate(taskId: "${taskId}", status: "completed", metadata: {result: "failed", error: "exit_code=N"})
194
- SendMessage(type: "message", recipient: "${leadName}", content: "실패: ${agentName} (exit=N)", summary: "task ${taskId} failed")
207
+ "timeout" 또는 "failed"이면:
208
+ TaskUpdate(taskId: "${taskId}", status: "completed", metadata: {result: "failed", error: "상태 메시지"})
209
+ SendMessage(type: "message", recipient: "${leadName}", content: "실패: ${agentName} (상태)", summary: "task ${taskId} failed")
195
210
  TFX_NEEDS_FALLBACK 출력 감지 시:
196
211
  TaskUpdate(taskId: "${taskId}", status: "completed", metadata: {result: "fallback", reason: "claude-native"})
197
212
  SendMessage(type: "message", recipient: "${leadName}", content: "fallback 필요: ${agentName} — claude-native 역할은 Claude Agent로 위임 필요", summary: "task ${taskId} fallback")
198
213
 
199
- Step 3 — TaskUpdate + SendMessage 후 즉시 종료. 추가 도구 호출 금지.`;
214
+ Step 5 — TaskUpdate + SendMessage 후 즉시 종료. 추가 도구 호출 금지.`;
200
215
  }
201
216
 
202
217
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "triflux",
3
- "version": "4.2.8",
3
+ "version": "4.2.9",
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": {
@@ -9,17 +9,111 @@
9
9
  # - Gemini health check 지수 백오프 (30×1s → 5×exp)
10
10
  # - 컨텍스트 파일 5번째 인자 지원
11
11
  #
12
- VERSION="2.4"
12
+ VERSION="2.5"
13
13
  #
14
14
  # 사용법:
15
15
  # tfx-route.sh <agent_type> <prompt> [mcp_profile] [timeout_sec] [context_file]
16
+ # tfx-route.sh --async <agent_type> <prompt> [mcp_profile] [timeout_sec] [context_file]
17
+ # tfx-route.sh --job-status <job_id>
18
+ # tfx-route.sh --job-result <job_id>
19
+ #
20
+ # --async: 백그라운드 실행, 즉시 job_id 반환 (Claude Code Bash 600초 제한 우회)
21
+ # --job-status: running | done | timeout | failed
22
+ # --job-result: 완료된 잡의 전체 출력
16
23
  #
17
24
  # 예시:
18
25
  # tfx-route.sh executor "코드 구현" implement
19
- # tfx-route.sh architect "아키텍처 분석" analyze '' context.md
26
+ # tfx-route.sh --async scientist " 리서치" auto 1440
27
+ # tfx-route.sh --job-status 1742400000-12345-9876
28
+ # tfx-route.sh --job-result 1742400000-12345-9876
20
29
 
21
30
  set -euo pipefail
22
31
 
32
+ # ── Async Job 디렉토리 ──
33
+ TFX_JOBS_DIR="${TMPDIR:-/tmp}/tfx-jobs"
34
+
35
+ # ── --job-status / --job-result 핸들러 (인자 파싱 전에 처리) ──
36
+ if [[ "${1:-}" == "--job-status" ]]; then
37
+ job_id="${2:?job_id 필수}"
38
+ job_dir="$TFX_JOBS_DIR/$job_id"
39
+ [[ -d "$job_dir" ]] || { echo "error: job not found"; exit 1; }
40
+
41
+ if [[ -f "$job_dir/done" ]]; then
42
+ exit_code=$(cat "$job_dir/exit_code" 2>/dev/null || echo 1)
43
+ if [[ "$exit_code" -eq 0 ]]; then
44
+ echo "done"
45
+ elif [[ "$exit_code" -eq 124 ]]; then
46
+ echo "timeout"
47
+ else
48
+ echo "failed"
49
+ fi
50
+ elif [[ -f "$job_dir/pid" ]]; then
51
+ pid=$(cat "$job_dir/pid")
52
+ if kill -0 "$pid" 2>/dev/null; then
53
+ # 진행 상황 힌트
54
+ local_bytes=$(wc -c < "$job_dir/stdout.log" 2>/dev/null || echo 0)
55
+ elapsed=$(( $(date +%s) - $(cat "$job_dir/start_time" 2>/dev/null || date +%s) ))
56
+ echo "running elapsed=${elapsed}s output=${local_bytes}B"
57
+ else
58
+ # 프로세스 종료됐는데 done 마커 없음 → 비정상 종료
59
+ echo "failed"
60
+ fi
61
+ else
62
+ echo "error: invalid job state"
63
+ exit 1
64
+ fi
65
+ exit 0
66
+ fi
67
+
68
+ if [[ "${1:-}" == "--job-result" ]]; then
69
+ job_id="${2:?job_id 필수}"
70
+ job_dir="$TFX_JOBS_DIR/$job_id"
71
+ [[ -d "$job_dir" ]] || { echo "error: job not found"; exit 1; }
72
+ [[ -f "$job_dir/done" ]] || { echo "error: job still running"; exit 1; }
73
+
74
+ cat "$job_dir/result.log" 2>/dev/null
75
+ exit_code=$(cat "$job_dir/exit_code" 2>/dev/null || echo 1)
76
+ exit "$exit_code"
77
+ fi
78
+
79
+ # ── --job-wait: 내부 폴링으로 완료 대기 (Bash 도구 호출 횟수 최소화) ──
80
+ # 사용법: tfx-route.sh --job-wait <job_id> [max_seconds=540]
81
+ # 출력: 주기적 "waiting elapsed=Ns" + 최종 "done"|"timeout"|"failed"|"still_running"
82
+ if [[ "${1:-}" == "--job-wait" ]]; then
83
+ job_id="${2:?job_id 필수}"
84
+ max_wait="${3:-540}" # 기본 540초 (9분, Bash 도구 600초 제한 이내)
85
+ poll_interval=15
86
+ job_dir="$TFX_JOBS_DIR/$job_id"
87
+ [[ -d "$job_dir" ]] || { echo "error: job not found"; exit 1; }
88
+
89
+ elapsed=0
90
+ while [[ "$elapsed" -lt "$max_wait" ]]; do
91
+ if [[ -f "$job_dir/done" ]]; then
92
+ ec=$(cat "$job_dir/exit_code" 2>/dev/null || echo 1)
93
+ if [[ "$ec" -eq 0 ]]; then echo "done"
94
+ elif [[ "$ec" -eq 124 ]]; then echo "timeout"
95
+ else echo "failed (exit=$ec)"
96
+ fi
97
+ exit 0
98
+ fi
99
+ sleep "$poll_interval"
100
+ elapsed=$((elapsed + poll_interval))
101
+ stderr_bytes=$(wc -c < "$job_dir/stderr.log" 2>/dev/null || echo 0)
102
+ echo "waiting elapsed=${elapsed}s progress=${stderr_bytes}B"
103
+ done
104
+
105
+ # max_wait 도달했지만 아직 실행 중
106
+ echo "still_running elapsed=${elapsed}s"
107
+ exit 0
108
+ fi
109
+
110
+ # ── --async 플래그 감지 ──
111
+ TFX_ASYNC_MODE=0
112
+ if [[ "${1:-}" == "--async" ]]; then
113
+ TFX_ASYNC_MODE=1
114
+ shift
115
+ fi
116
+
23
117
  # ── 인자 파싱 ──
24
118
  AGENT_TYPE="${1:?에이전트 타입 필수 (executor, debugger, designer 등)}"
25
119
  PROMPT="${2:?프롬프트 필수}"
@@ -1518,4 +1612,40 @@ EOF
1518
1612
  return "$exit_code"
1519
1613
  }
1520
1614
 
1615
+ # ── Async 모드: 백그라운드 실행 + 즉시 job_id 반환 ──
1616
+ if [[ "$TFX_ASYNC_MODE" -eq 1 ]]; then
1617
+ mkdir -p "$TFX_JOBS_DIR"
1618
+ JOB_ID="$TIMESTAMP-$$-${RANDOM}"
1619
+ JOB_DIR="$TFX_JOBS_DIR/$JOB_ID"
1620
+ mkdir -p "$JOB_DIR"
1621
+ echo "$AGENT_TYPE" > "$JOB_DIR/agent_type"
1622
+ date +%s > "$JOB_DIR/start_time"
1623
+
1624
+ # 백그라운드 서브쉘: main 실행 → 결과 저장
1625
+ (
1626
+ set +e # main 내부 에러가 exit_code 기록 전에 서브쉘을 죽이는 것 방지
1627
+ exec > "$JOB_DIR/result.log" 2>"$JOB_DIR/stderr.log"
1628
+ main
1629
+ echo $? > "$JOB_DIR/exit_code"
1630
+ touch "$JOB_DIR/done"
1631
+ ) &
1632
+ bg_pid=$!
1633
+ echo "$bg_pid" > "$JOB_DIR/pid"
1634
+
1635
+ # 종료 감지 데몬 (main이 signal/crash로 죽어도 done 마커 생성)
1636
+ (
1637
+ wait "$bg_pid" 2>/dev/null
1638
+ ec=$?
1639
+ if [[ ! -f "$JOB_DIR/done" ]]; then
1640
+ echo "$ec" > "$JOB_DIR/exit_code"
1641
+ touch "$JOB_DIR/done"
1642
+ fi
1643
+ ) &
1644
+ disown
1645
+
1646
+ # 즉시 리턴: 1초 이내에 Claude Code Bash 도구 완료
1647
+ echo "$JOB_ID"
1648
+ exit 0
1649
+ fi
1650
+
1521
1651
  main
@@ -170,7 +170,7 @@ status는 "completed"만 사용. 실패 여부는 `metadata.result`로 구분.
170
170
 
171
171
  | 항목 | 설명 |
172
172
  |------|------|
173
- | `scripts/tfx-route.sh` | 팀 통합 라우터 |
173
+ | `scripts/tfx-route.sh` | 팀 통합 라우터 (v2.5: `--async`/`--job-wait`/`--job-status`/`--job-result`) |
174
174
  | `hub/team/native.mjs` | Native Teams 래퍼 (프롬프트 템플릿) |
175
175
  | `hub/pipeline/` | 파이프라인 상태 기계 (`--thorough` 모드) |
176
176
  | `tfx-auto` | one-shot 실행 오케스트레이터 |
@@ -58,12 +58,25 @@ codex-worker는 반드시 tfx-route.sh를 통해 Codex에 위임하고, gemini-w
58
58
 
59
59
  리드는 워커의 Step 2, Step 4 시점에 턴 경계를 인식하고, 방향 전환/추가 지시/재실행 요청을 보낼 수 있다.
60
60
 
61
- ## Bash timeout 동적 상속
61
+ ## Async 실행 프로토콜 (v2.5+)
62
62
 
63
- Bash timeout은 tfx-route.sh의 role/profile별 timeout + 60여유를 ms로 변환하여 동적 상속한다.
64
- `getRouteTimeout(role, mcpProfile)` 기준:
65
- - analyze/review 프로필 또는 architect/analyst 역할: 3600초
66
- - 기본: 1080초(18분)
63
+ Claude Code Bash 도구는 최대 600(10분) 하드코딩 제한이 있다.
64
+ scientist(24분), scientist-deep(60분) 등 장시간 워커는 이 제한에 걸린다.
65
+
66
+ **해결: `--async` 3단계 패턴**
67
+
68
+ | 단계 | 명령 | Bash timeout | 소요 |
69
+ |------|------|-------------|------|
70
+ | 시작 | `tfx-route.sh --async {role} '{task}' {profile} {timeout}` | 15초 | <1초 |
71
+ | 대기 | `tfx-route.sh --job-wait {job_id} 540` | 570초 | 최대 540초/회 |
72
+ | 결과 | `tfx-route.sh --job-result {job_id}` | 30초 | <1초 |
73
+
74
+ - `--job-wait`는 내부에서 15초 간격으로 폴링하며 `done`/`timeout`/`failed`/`still_running` 반환
75
+ - `still_running` 시 같은 `--job-wait` 명령을 반복 (무한 반복 가능)
76
+ - 실제 워커 timeout은 tfx-route.sh의 `timeout` 명령으로 관리 (Bash 도구와 무관)
77
+
78
+ **이전 방식 (deprecated):**
79
+ Bash timeout을 role/profile별 timeout + 60초로 설정했으나, 600초 초과 시 Bash 도구가 강제 종료했다.
67
80
 
68
81
  ## tfx-route.sh 팀 통합 동작
69
82