triflux 3.2.0-dev.15 → 3.2.0-dev.17

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.
@@ -80,6 +80,77 @@ Bash(command: 'TFX_TEAM_NAME="${teamName}" TFX_TEAM_TASK_ID="${taskId}" TFX_TEAM
80
80
  실패 여부는 metadata.result로 구분. Bash 실패 시에도 반드시 TaskUpdate + SendMessage 후 종료.`;
81
81
  }
82
82
 
83
+ /**
84
+ * v3 하이브리드 래퍼 프롬프트 생성
85
+ * psmux pane 기반 비동기 실행 + polling 패턴.
86
+ * Agent가 idle 상태를 유지하여 인터럽트 수신이 가능하다.
87
+ *
88
+ * @param {'codex'|'gemini'} cli — CLI 타입
89
+ * @param {object} opts
90
+ * @param {string} opts.subtask — 서브태스크 설명
91
+ * @param {string} [opts.role] — 역할
92
+ * @param {string} [opts.teamName] — 팀 이름
93
+ * @param {string} [opts.taskId] — Hub task ID
94
+ * @param {string} [opts.agentName] — 워커 표시 이름
95
+ * @param {string} [opts.leadName] — 리드 수신자 이름
96
+ * @param {string} [opts.mcp_profile] — MCP 프로필
97
+ * @param {string} [opts.sessionName] — psmux 세션 이름
98
+ * @param {string} [opts.pipelinePhase] — 파이프라인 단계
99
+ * @param {string} [opts.psmuxPath] — psmux.mjs 경로
100
+ * @returns {string} 하이브리드 래퍼 프롬프트
101
+ */
102
+ export function buildHybridWrapperPrompt(cli, opts = {}) {
103
+ const {
104
+ subtask,
105
+ role = "executor",
106
+ teamName = "tfx-multi",
107
+ taskId = "",
108
+ agentName = "",
109
+ leadName = "team-lead",
110
+ mcp_profile = "auto",
111
+ sessionName = teamName,
112
+ pipelinePhase = "",
113
+ psmuxPath = "hub/team/psmux.mjs",
114
+ } = opts;
115
+
116
+ const escaped = subtask.replace(/'/g, "'\\''");
117
+ const pipelineHint = pipelinePhase ? `\n파이프라인 단계: ${pipelinePhase}` : "";
118
+ const taskIdRef = taskId ? `taskId: "${taskId}"` : "";
119
+ const taskIdArg = taskIdRef ? `${taskIdRef}, ` : "";
120
+
121
+ const routeCmd = `TFX_TEAM_NAME="${teamName}" TFX_TEAM_TASK_ID="${taskId}" TFX_TEAM_AGENT_NAME="${agentName}" TFX_TEAM_LEAD_NAME="${leadName}" bash ${ROUTE_SCRIPT} "${role}" '${escaped}' ${mcp_profile}`;
122
+
123
+ return `하이브리드 psmux 워커 프로토콜:
124
+
125
+ 1. TaskUpdate(${taskIdArg}status: in_progress) + SendMessage(to: ${leadName}, "작업 시작: ${agentName}")
126
+
127
+ 2. pane 생성 (비동기 실행):
128
+ Bash: node ${psmuxPath} spawn --session "${sessionName}" --name "${agentName}" --cmd "${routeCmd}"
129
+
130
+ 3. 폴링 루프 (10초 간격, idle 유지 → 인터럽트 수신 가능):
131
+ Bash: node ${psmuxPath} status --session "${sessionName}" --name "${agentName}"
132
+ - status: "running" → 10초 대기 후 재확인
133
+ - status: "exited" → 5단계로
134
+
135
+ 4. 인터럽트 수신 시:
136
+ Bash: node ${psmuxPath} kill --session "${sessionName}" --name "${agentName}"
137
+ → SendMessage(to: ${leadName}, "인터럽트 수신, 방향 전환")
138
+ → 새 지시에 따라 2단계부터 재실행
139
+
140
+ 5. 완료 시:
141
+ Bash: node ${psmuxPath} output --session "${sessionName}" --name "${agentName}" --lines 100
142
+ → 결과를 TaskUpdate + SendMessage로 보고
143
+ ${pipelineHint}
144
+ gemini/codex를 직접 호출하지 마라. psmux spawn이 tfx-route.sh를 통해 실행한다.
145
+ 프롬프트를 파일로 저장하지 마라. psmux spawn --cmd 인자로 전달된다.
146
+
147
+ 성공 → TaskUpdate(${taskIdArg}status: completed, metadata: {result: "success"}) + SendMessage(to: ${leadName}).
148
+ 실패 → TaskUpdate(${taskIdArg}status: completed, metadata: {result: "failed", error: "에러 요약"}) + SendMessage(to: ${leadName}).
149
+
150
+ 중요: TaskUpdate의 status는 "completed"만 사용. "failed"는 API 미지원.
151
+ 실패 여부는 metadata.result로 구분. pane 실패 시에도 반드시 TaskUpdate + SendMessage 후 종료.`;
152
+ }
153
+
83
154
  /**
84
155
  * 팀 이름 생성 (타임스탬프 기반)
85
156
  * @returns {string}
@@ -3,6 +3,8 @@
3
3
  import { execSync, spawnSync } from "node:child_process";
4
4
 
5
5
  const PSMUX_BIN = process.env.PSMUX_BIN || "psmux";
6
+ const GIT_BASH = process.env.GIT_BASH_PATH || "C:\\Program Files\\Git\\bin\\bash.exe";
7
+ const IS_WINDOWS = process.platform === "win32";
6
8
 
7
9
  function quoteArg(value) {
8
10
  const str = String(value);
@@ -309,12 +311,26 @@ export function spawnWorker(sessionName, workerName, cmd) {
309
311
  if (!hasPsmux()) {
310
312
  throw new Error("psmux가 설치되어 있지 않습니다. psmux를 먼저 설치하세요.");
311
313
  }
314
+
315
+ // remain-on-exit: 종료된 pane이 즉시 사라지는 것 방지
312
316
  try {
313
- // 세션 컨텍스트 포함 타겟 반환 (psmux는 세션별 서버 모델)
314
- const paneTarget = psmuxExec(
315
- `split-window -t ${quoteArg(sessionName)} -P -F "#{session_name}:#{window_index}.#{pane_index}" ${quoteArg(cmd)}`
316
- );
317
- psmuxExec(`select-pane -t ${quoteArg(paneTarget)} -T ${quoteArg(workerName)}`);
317
+ psmuxExec(`set-option -t ${quoteArg(sessionName)} remain-on-exit on`);
318
+ } catch { /* 미지원 시 무시 */ }
319
+
320
+ // Windows: pane 기본셸이 PowerShell → Git Bash로 래핑
321
+ // psmux가 이스케이프 시퀀스를 처리하므로 포워드 슬래시로 변환
322
+ const shellCmd = IS_WINDOWS
323
+ ? `& '${GIT_BASH.replace(/\\/g, '/')}' -l -c '${cmd.replace(/'/g, "'\\''")}'`
324
+ : cmd;
325
+
326
+ try {
327
+ // 배열 형태 spawnSync → 쉘 해석 우회 (백슬래시 경로 보존)
328
+ const paneTarget = psmux([
329
+ "split-window", "-t", sessionName,
330
+ "-P", "-F", "#{session_name}:#{window_index}.#{pane_index}",
331
+ shellCmd,
332
+ ]);
333
+ psmux(["select-pane", "-t", paneTarget, "-T", workerName]);
318
334
  return { paneId: paneTarget, workerName };
319
335
  } catch (err) {
320
336
  throw new Error(`워커 생성 실패 (session=${sessionName}, worker=${workerName}): ${err.message}`);
@@ -365,9 +381,15 @@ export function killWorker(sessionName, workerName) {
365
381
  throw new Error("psmux가 설치되어 있지 않습니다.");
366
382
  }
367
383
  try {
368
- // paneId 찾기
369
- const { paneId } = getWorkerStatus(sessionName, workerName);
370
- // C-c로 우아한 종료 시도
384
+ const { paneId, status } = getWorkerStatus(sessionName, workerName);
385
+
386
+ // 이미 종료된 워커 → pane 정리만 수행
387
+ if (status === "exited") {
388
+ try { psmuxExec(`kill-pane -t ${quoteArg(paneId)}`); } catch { /* 무시 */ }
389
+ return { killed: true };
390
+ }
391
+
392
+ // running → C-c 우아한 종료 시도
371
393
  try {
372
394
  psmuxExec(`send-keys -t ${quoteArg(paneId)} C-c`);
373
395
  } catch {
@@ -382,8 +404,9 @@ export function killWorker(sessionName, workerName) {
382
404
  }
383
405
  return { killed: true };
384
406
  } catch (err) {
407
+ // 워커를 찾을 수 없음 → 이미 종료된 것으로 간주
385
408
  if (err.message.includes("워커를 찾을 수 없습니다")) {
386
- return { killed: false };
409
+ return { killed: true };
387
410
  }
388
411
  throw new Error(`워커 종료 실패 (session=${sessionName}, worker=${workerName}): ${err.message}`);
389
412
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "triflux",
3
- "version": "3.2.0-dev.15",
3
+ "version": "3.2.0-dev.17",
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": {
package/scripts/setup.mjs CHANGED
@@ -16,10 +16,17 @@ const CODEX_DIR = join(homedir(), ".codex");
16
16
  const CODEX_CONFIG_PATH = join(CODEX_DIR, "config.toml");
17
17
 
18
18
  const REQUIRED_CODEX_PROFILES = [
19
+ {
20
+ name: "high",
21
+ lines: [
22
+ 'model = "gpt-5.4"',
23
+ 'model_reasoning_effort = "high"',
24
+ ],
25
+ },
19
26
  {
20
27
  name: "xhigh",
21
28
  lines: [
22
- 'model = "gpt-5.3-codex"',
29
+ 'model = "gpt-5.4"',
23
30
  'model_reasoning_effort = "xhigh"',
24
31
  ],
25
32
  },
@@ -447,7 +447,7 @@ get_gemini_mcp_servers() {
447
447
  local profile="$1"
448
448
  case "$profile" in
449
449
  implement) echo "context7 brave-search" ;;
450
- analyze) echo "context7 brave-search exa" ;;
450
+ analyze) echo "context7 brave-search exa tavily" ;;
451
451
  review) echo "sequential-thinking" ;;
452
452
  docs) echo "context7 brave-search" ;;
453
453
  *) echo "" ;;
@@ -13,6 +13,11 @@ argument-hint: '"작업 설명" | --agents codex,gemini "작업" | --tmux "작
13
13
  > 실제 작업은 `tfx-route.sh`가 수행한다. task 상태는 `team_task_list`를 truth source로 검증한다.
14
14
  > v3 — `--quick`(기본, v2.2 호환) + `--thorough`(전체 파이프라인: plan→prd→exec→verify→fix loop).
15
15
 
16
+ > **[필수] Lead 웹 서치 직접 사용 금지**
17
+ > Lead(Claude Opus)는 brave-search, exa, tavily 등 웹 서치 MCP 도구를 직접 호출하지 마라.
18
+ > 리서치/검색 태스크는 반드시 `scientist` 또는 `document-specialist` 역할의 Codex 워커에 위임하라.
19
+ > Lead가 MCP 검색 도구에 직접 접근 가능하더라도, 위임해야 Claude 토큰을 절약할 수 있다.
20
+
16
21
  ## 사용법
17
22
 
18
23
  ```