triflux 3.3.0-dev.3 → 3.3.0-dev.5

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.
@@ -1,200 +1,266 @@
1
- // hub/team/native.mjs — Claude Native Teams 래퍼
2
- // teammate 프롬프트 템플릿 + 팀 설정 빌더
3
- //
4
- // Claude Code 네이티브 Agent Teams (CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1)
5
- // 환경에서 teammate를 Codex/Gemini CLI 래퍼로 구성하는 유틸리티.
6
- // SKILL.md가 인라인 프롬프트를 사용하므로, 이 모듈은 CLI(tfx multi --native)에서
7
- // 팀 설정을 프로그래밍적으로 생성할 때 사용한다.
8
-
9
- const ROUTE_SCRIPT = "~/.claude/scripts/tfx-route.sh";
10
-
11
- function inferWorkerIndex(agentName = "") {
12
- const match = /(\d+)(?!.*\d)/.exec(agentName);
13
- if (!match) return null;
14
- const index = Number(match[1]);
15
- return Number.isInteger(index) && index > 0 ? index : null;
16
- }
17
-
18
- function buildRouteEnvPrefix(agentName, workerIndex, searchTool) {
19
- const effectiveWorkerIndex = Number.isInteger(workerIndex) && workerIndex > 0
20
- ? workerIndex
21
- : inferWorkerIndex(agentName);
22
-
23
- let envPrefix = "";
24
- if (effectiveWorkerIndex) envPrefix += ` TFX_WORKER_INDEX="${effectiveWorkerIndex}"`;
25
- if (searchTool) envPrefix += ` TFX_SEARCH_TOOL="${searchTool}"`;
26
- return envPrefix;
27
- }
28
-
29
- /**
30
- * role/mcp_profile별 tfx-route.sh 기본 timeout (초)
31
- * analyze/review 프로필이나 설계·분석 역할은 더 긴 timeout을 부여한다.
32
- * @param {string} role — 워커 역할
33
- * @param {string} mcpProfile — MCP 프로필
34
- * @returns {number} timeout(초)
35
- */
36
- function getRouteTimeout(role, mcpProfile) {
37
- if (mcpProfile === "analyze" || mcpProfile === "review") return 3600;
38
- if (role === "architect" || role === "analyst") return 3600;
39
- return 1080; // 기본 18분
40
- }
41
-
42
- /**
43
- * v2.2 슬림 래퍼 프롬프트 생성
44
- * Agent spawn으로 네비게이션에 등록하되, 실제 작업은 tfx-route.sh가 수행.
45
- * 프롬프트 ~100 토큰 목표 (v2의 ~500 대비 80% 감소).
46
- *
47
- * @param {'codex'|'gemini'} cli — CLI 타입
48
- * @param {object} opts
49
- * @param {string} opts.subtask — 서브태스크 설명
50
- * @param {string} [opts.role] — 역할 (executor, designer, reviewer 등)
51
- * @param {string} [opts.teamName] 이름
52
- * @param {string} [opts.taskId] Hub task ID
53
- * @param {string} [opts.agentName] — 워커 표시 이름
54
- * @param {string} [opts.leadName] — 리드 수신자 이름
55
- * @param {string} [opts.mcp_profile] — MCP 프로필
56
- * @param {number} [opts.workerIndex] — 검색 힌트 회전에 사용할 워커 인덱스(1-based)
57
- * @param {string} [opts.searchTool] — 전용 검색 도구 힌트(brave-search|tavily|exa)
58
- * @param {number} [opts.bashTimeout] — Bash timeout(ms). 미지정 시 role/profile 기반 자동 산출.
59
- * @returns {string} 슬림 래퍼 프롬프트
60
- */
61
- export function buildSlimWrapperPrompt(cli, opts = {}) {
62
- const {
63
- subtask,
64
- role = "executor",
65
- teamName = "tfx-multi",
66
- taskId = "",
67
- agentName = "",
68
- leadName = "team-lead",
69
- mcp_profile = "auto",
70
- workerIndex,
71
- searchTool = "",
72
- pipelinePhase = "",
73
- bashTimeout,
74
- } = opts;
75
-
76
- // role/profile 기반 timeout 산출 (기본 timeout + 60초 여유, ms 변환)
77
- const bashTimeoutMs = bashTimeout ?? (getRouteTimeout(role, mcp_profile) + 60) * 1000;
78
-
79
- // 셸 이스케이프
80
- const escaped = subtask.replace(/'/g, "'\\''");
81
- const pipelineHint = pipelinePhase
82
- ? `\n파이프라인 단계: ${pipelinePhase}`
83
- : '';
84
- const routeEnvPrefix = buildRouteEnvPrefix(agentName, workerIndex, searchTool);
85
-
86
- const taskIdRef = taskId ? `taskId: "${taskId}"` : "";
87
-
88
- return `인터럽트 프로토콜:
89
- 1. TaskUpdate(${taskIdRef ? `${taskIdRef}, ` : ""}status: in_progress) — task claim
90
- 2. SendMessage(to: ${leadName}, "작업 시작: ${agentName}") — 시작 보고 (턴 경계 생성)
91
- 3. Bash(command, timeout: ${bashTimeoutMs}) — 아래 명령 1회 실행
92
- 4. 결과 보고 반드시 종료${pipelineHint}
93
-
94
- [HARD CONSTRAINT] 너는 Bash, TaskUpdate, TaskGet, TaskList, SendMessage만 사용할 수 있다.
95
- Read, Edit, Write, Grep, Glob, Agent, WebSearch, WebFetch 등 다른 모든 도구 사용을 금지한다.
96
- 코드를 직접 읽거나 수정하면 안 된다. 반드시 아래 Bash 명령(tfx-route.sh)을 통해 Codex/Gemini에 위임하라.
97
- 규칙을 위반하면 작업 실패로 간주한다.
98
-
99
- gemini/codex를 직접 호출하지 마라. 반드시 tfx-route.sh를 거쳐야 한다.
100
- 프롬프트를 파일로 저장하지 마라. tfx-route.sh가 인자로 받는다.
101
-
102
- 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})
103
-
104
- 성공 TaskUpdate(${taskIdRef ? `${taskIdRef}, ` : ""}status: completed, metadata: {result: "success"}) + SendMessage(to: ${leadName}).
105
- 실패 TaskUpdate(${taskIdRef ? `${taskIdRef}, ` : ""}status: completed, metadata: {result: "failed", error: "에러 요약"}) + SendMessage(to: ${leadName}).
106
-
107
- 중요: TaskUpdate의 status는 "completed"만 사용. "failed"는 API 미지원.
108
- 실패 여부는 metadata.result로 구분. Bash 실패 시에도 반드시 TaskUpdate + SendMessage 후 종료.`;
109
- }
110
-
111
- /**
112
- * v3 하이브리드 래퍼 프롬프트 생성
113
- * psmux pane 기반 비동기 실행 + polling 패턴.
114
- * Agent가 idle 상태를 유지하여 인터럽트 수신이 가능하다.
115
- *
116
- * @param {'codex'|'gemini'} cliCLI 타입
117
- * @param {object} opts
118
- * @param {string} opts.subtask서브태스크 설명
119
- * @param {string} [opts.role] — 역할
120
- * @param {string} [opts.teamName] 팀 이름
121
- * @param {string} [opts.taskId] — Hub task ID
122
- * @param {string} [opts.agentName] 워커 표시 이름
123
- * @param {string} [opts.leadName] — 리드 수신자 이름
124
- * @param {string} [opts.mcp_profile] — MCP 프로필
125
- * @param {number} [opts.workerIndex] — 검색 힌트 회전에 사용할 워커 인덱스(1-based)
126
- * @param {string} [opts.searchTool] — 전용 검색 도구 힌트(brave-search|tavily|exa)
127
- * @param {string} [opts.sessionName] — psmux 세션 이름
128
- * @param {string} [opts.pipelinePhase] — 파이프라인 단계
129
- * @param {string} [opts.psmuxPath] — psmux.mjs 경로
130
- * @returns {string} 하이브리드 래퍼 프롬프트
131
- */
132
- export function buildHybridWrapperPrompt(cli, opts = {}) {
133
- const {
134
- subtask,
135
- role = "executor",
136
- teamName = "tfx-multi",
137
- taskId = "",
138
- agentName = "",
139
- leadName = "team-lead",
140
- mcp_profile = "auto",
141
- workerIndex,
142
- searchTool = "",
143
- sessionName = teamName,
144
- pipelinePhase = "",
145
- psmuxPath = "hub/team/psmux.mjs",
146
- } = opts;
147
-
148
- const escaped = subtask.replace(/'/g, "'\\''");
149
- const pipelineHint = pipelinePhase ? `\n파이프라인 단계: ${pipelinePhase}` : "";
150
- const taskIdRef = taskId ? `taskId: "${taskId}"` : "";
151
- const taskIdArg = taskIdRef ? `${taskIdRef}, ` : "";
152
- const routeEnvPrefix = buildRouteEnvPrefix(agentName, workerIndex, searchTool);
153
-
154
- const routeCmd = `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}`;
155
-
156
- return `하이브리드 psmux 워커 프로토콜:
157
-
158
- 1. TaskUpdate(${taskIdArg}status: in_progress) + SendMessage(to: ${leadName}, "작업 시작: ${agentName}")
159
-
160
- 2. pane 생성 (비동기 실행):
161
- Bash: node ${psmuxPath} spawn --session "${sessionName}" --name "${agentName}" --cmd "${routeCmd}"
162
-
163
- 3. 폴링 루프 (10초 간격, idle 유지 인터럽트 수신 가능):
164
- Bash: node ${psmuxPath} status --session "${sessionName}" --name "${agentName}"
165
- - status: "running" 10초 대기 후 재확인
166
- - status: "exited" 5단계로
167
-
168
- 4. 인터럽트 수신 시:
169
- Bash: node ${psmuxPath} kill --session "${sessionName}" --name "${agentName}"
170
- SendMessage(to: ${leadName}, "인터럽트 수신, 방향 전환")
171
- 지시에 따라 2단계부터 재실행
172
-
173
- 5. 완료 시:
174
- Bash: node ${psmuxPath} output --session "${sessionName}" --name "${agentName}" --lines 100
175
- → 결과를 TaskUpdate + SendMessage로 보고
176
- ${pipelineHint}
177
- [HARD CONSTRAINT] 너는 Bash, TaskUpdate, TaskGet, TaskList, SendMessage만 사용할 수 있다.
178
- Read, Edit, Write, Grep, Glob, Agent, WebSearch, WebFetch 등 다른 모든 도구 사용을 금지한다.
179
- 코드를 직접 읽거나 수정하면 된다. 반드시 아래 Bash 명령(tfx-route.sh)을 통해 Codex/Gemini에 위임하라.
180
- 규칙을 위반하면 작업 실패로 간주한다.
181
-
182
- gemini/codex를 직접 호출하지 마라. psmux spawn이 tfx-route.sh를 통해 실행한다.
183
- 프롬프트를 파일로 저장하지 마라. psmux spawn --cmd 인자로 전달된다.
184
-
185
- 성공 TaskUpdate(${taskIdArg}status: completed, metadata: {result: "success"}) + SendMessage(to: ${leadName}).
186
- 실패 TaskUpdate(${taskIdArg}status: completed, metadata: {result: "failed", error: "에러 요약"}) + SendMessage(to: ${leadName}).
187
-
188
- 중요: TaskUpdate의 status는 "completed"만 사용. "failed"는 API 미지원.
189
- 실패 여부는 metadata.result로 구분. pane 실패 시에도 반드시 TaskUpdate + SendMessage 후 종료.`;
190
- }
191
-
192
- /**
193
- * 이름 생성 (타임스탬프 기반)
194
- * @returns {string}
195
- */
196
- export function generateTeamName() {
197
- const ts = Date.now().toString(36).slice(-4);
198
- const rand = Math.random().toString(36).slice(2, 6);
199
- return `tfx-${ts}${rand}`;
200
- }
1
+ // hub/team/native.mjs — Claude Native Teams 래퍼
2
+ // teammate 프롬프트 템플릿 + 팀 설정 빌더
3
+ //
4
+ // Claude Code 네이티브 Agent Teams (CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1)
5
+ // 환경에서 teammate를 Codex/Gemini CLI 래퍼로 구성하는 유틸리티.
6
+ // SKILL.md가 인라인 프롬프트를 사용하므로, 이 모듈은 CLI(tfx multi --native)에서
7
+ // 팀 설정을 프로그래밍적으로 생성할 때 사용한다.
8
+
9
+ const ROUTE_SCRIPT = "~/.claude/scripts/tfx-route.sh";
10
+ export const SLIM_WRAPPER_SUBAGENT_TYPE = "slim-wrapper";
11
+ const ROUTE_LOG_RE = /\[tfx-route\]/i;
12
+ const ROUTE_COMMAND_RE = /(?:^|[\s"'`])(?:bash\s+)?(?:[^"'`\s]*\/)?tfx-route\.sh\b/i;
13
+ const ROUTE_PROMPT_RE = /tfx-route\.sh/i;
14
+
15
+ function inferWorkerIndex(agentName = "") {
16
+ const match = /(\d+)(?!.*\d)/.exec(agentName);
17
+ if (!match) return null;
18
+ const index = Number(match[1]);
19
+ return Number.isInteger(index) && index > 0 ? index : null;
20
+ }
21
+
22
+ function buildRouteEnvPrefix(agentName, workerIndex, searchTool) {
23
+ const effectiveWorkerIndex = Number.isInteger(workerIndex) && workerIndex > 0
24
+ ? workerIndex
25
+ : inferWorkerIndex(agentName);
26
+
27
+ let envPrefix = "";
28
+ if (effectiveWorkerIndex) envPrefix += ` TFX_WORKER_INDEX="${effectiveWorkerIndex}"`;
29
+ if (searchTool) envPrefix += ` TFX_SEARCH_TOOL="${searchTool}"`;
30
+ return envPrefix;
31
+ }
32
+
33
+ /**
34
+ * slim-wrapper 커스텀 subagent 사양.
35
+ * Claude Code custom subagent(`.claude/agents/slim-wrapper.md`)와 짝을 이룬다.
36
+ *
37
+ * @param {'codex'|'gemini'} cli
38
+ * @param {object} opts
39
+ * @returns {{name:string, cli:string, subagent_type:string, prompt:string}}
40
+ */
41
+ export function buildSlimWrapperAgent(cli, opts = {}) {
42
+ return {
43
+ name: opts.agentName || `${cli}-wrapper`,
44
+ cli,
45
+ subagent_type: SLIM_WRAPPER_SUBAGENT_TYPE,
46
+ prompt: buildSlimWrapperPrompt(cli, opts),
47
+ };
48
+ }
49
+
50
+ /**
51
+ * slim-wrapper 로그에서 tfx-route.sh 경유 흔적을 판정한다.
52
+ * route stderr prefix(`[tfx-route]`) 또는 Bash command trace를 근거로 본다.
53
+ *
54
+ * @param {object} input
55
+ * @param {string} [input.promptText]
56
+ * @param {string} [input.stdoutText]
57
+ * @param {string} [input.stderrText]
58
+ * @returns {{
59
+ * expectedRouteInvocation: boolean,
60
+ * promptMentionsRoute: boolean,
61
+ * sawRouteCommand: boolean,
62
+ * sawRouteLog: boolean,
63
+ * usedRoute: boolean,
64
+ * abnormal: boolean,
65
+ * reason: string|null,
66
+ * }}
67
+ */
68
+ export function verifySlimWrapperRouteExecution(input = {}) {
69
+ const promptText = String(input.promptText || "");
70
+ const stdoutText = String(input.stdoutText || "");
71
+ const stderrText = String(input.stderrText || "");
72
+ const combinedLogs = `${stdoutText}\n${stderrText}`;
73
+ const promptMentionsRoute = ROUTE_PROMPT_RE.test(promptText);
74
+ const sawRouteCommand = ROUTE_COMMAND_RE.test(combinedLogs);
75
+ const sawRouteLog = ROUTE_LOG_RE.test(combinedLogs);
76
+ const usedRoute = sawRouteCommand || sawRouteLog;
77
+ const expectedRouteInvocation = promptMentionsRoute;
78
+
79
+ return {
80
+ expectedRouteInvocation,
81
+ promptMentionsRoute,
82
+ sawRouteCommand,
83
+ sawRouteLog,
84
+ usedRoute,
85
+ abnormal: expectedRouteInvocation && !usedRoute,
86
+ reason: expectedRouteInvocation && !usedRoute ? "missing_tfx_route_evidence" : null,
87
+ };
88
+ }
89
+
90
+ /**
91
+ * role/mcp_profile별 tfx-route.sh 기본 timeout (초)
92
+ * analyze/review 프로필이나 설계·분석 역할은 더 긴 timeout을 부여한다.
93
+ * @param {string} role — 워커 역할
94
+ * @param {string} mcpProfile MCP 프로필
95
+ * @returns {number} timeout(초)
96
+ */
97
+ function getRouteTimeout(role, mcpProfile) {
98
+ if (mcpProfile === "analyze" || mcpProfile === "review") return 3600;
99
+ if (role === "architect" || role === "analyst") return 3600;
100
+ return 1080; // 기본 18분
101
+ }
102
+
103
+ /**
104
+ * v2.2 슬림 래퍼 프롬프트 생성
105
+ * Agent spawn으로 네비게이션에 등록하되, 실제 작업은 tfx-route.sh가 수행.
106
+ * 프롬프트 ~100 토큰 목표 (v2의 ~500 대비 80% 감소).
107
+ *
108
+ * @param {'codex'|'gemini'} cli CLI 타입
109
+ * @param {object} opts
110
+ * @param {string} opts.subtask — 서브태스크 설명
111
+ * @param {string} [opts.role] — 역할 (executor, designer, reviewer 등)
112
+ * @param {string} [opts.teamName] 팀 이름
113
+ * @param {string} [opts.taskId] Hub task ID
114
+ * @param {string} [opts.agentName] 워커 표시 이름
115
+ * @param {string} [opts.leadName] — 리드 수신자 이름
116
+ * @param {string} [opts.mcp_profile]MCP 프로필
117
+ * @param {number} [opts.workerIndex] — 검색 힌트 회전에 사용할 워커 인덱스(1-based)
118
+ * @param {string} [opts.searchTool]전용 검색 도구 힌트(brave-search|tavily|exa)
119
+ * @param {number} [opts.bashTimeout] — Bash timeout(ms). 미지정 시 role/profile 기반 자동 산출.
120
+ * @returns {string} 슬림 래퍼 프롬프트
121
+ */
122
+ export function buildSlimWrapperPrompt(cli, opts = {}) {
123
+ const {
124
+ subtask,
125
+ role = "executor",
126
+ teamName = "tfx-multi",
127
+ taskId = "",
128
+ agentName = "",
129
+ leadName = "team-lead",
130
+ mcp_profile = "auto",
131
+ workerIndex,
132
+ searchTool = "",
133
+ pipelinePhase = "",
134
+ bashTimeout,
135
+ } = opts;
136
+
137
+ // role/profile 기반 timeout 산출 (기본 timeout + 60초 여유, ms 변환)
138
+ const bashTimeoutMs = bashTimeout ?? (getRouteTimeout(role, mcp_profile) + 60) * 1000;
139
+
140
+ // 이스케이프
141
+ const escaped = subtask.replace(/'/g, "'\\''");
142
+ const pipelineHint = pipelinePhase
143
+ ? `\n파이프라인 단계: ${pipelinePhase}`
144
+ : '';
145
+ const routeEnvPrefix = buildRouteEnvPrefix(agentName, workerIndex, searchTool);
146
+
147
+ return `실행 프로토콜 (subagent_type="${SLIM_WRAPPER_SUBAGENT_TYPE}"):
148
+ 1. Bash(command, timeout: ${bashTimeoutMs}) — 아래 명령 1회만 실행
149
+ 2. Bash 종료 TaskUpdate + SendMessage로 Claude Code 태스크 동기화
150
+ 3. 종료${pipelineHint}
151
+
152
+ [HARD CONSTRAINT] 허용 도구: Bash, TaskUpdate, SendMessage만 사용한다.
153
+ Read, Edit, Write, Grep, Glob, Agent, WebSearch, WebFetch 등 다른 모든 도구 사용을 금지한다.
154
+ 코드를 직접 읽거나 수정하면 된다. 반드시 아래 Bash 명령(tfx-route.sh)을 통해 Codex/Gemini에 위임하라.
155
+ 이 규칙을 위반하면 작업 실패로 간주한다.
156
+
157
+ gemini/codex를 직접 호출하지 마라. 반드시 tfx-route.sh를 거쳐야 한다.
158
+ 프롬프트를 파일로 저장하지 마라. tfx-route.sh가 인자로 받는다.
159
+
160
+ Step 1 Bash 실행:
161
+ 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})
162
+
163
+ Step 2 Claude Code 태스크 동기화 (Bash 완료 반드시 실행):
164
+ exit_code=0이면:
165
+ TaskUpdate(taskId: "${taskId}", status: "completed", metadata: {result: "success"})
166
+ SendMessage(type: "message", recipient: "${leadName}", content: "완료: ${agentName}", summary: "task ${taskId} success")
167
+ exit_code≠0이면:
168
+ TaskUpdate(taskId: "${taskId}", status: "completed", metadata: {result: "failed", error: "exit_code=N"})
169
+ SendMessage(type: "message", recipient: "${leadName}", content: "실패: ${agentName} (exit=N)", summary: "task ${taskId} failed")
170
+ TFX_NEEDS_FALLBACK 출력 감지 시:
171
+ TaskUpdate(taskId: "${taskId}", status: "completed", metadata: {result: "fallback", reason: "claude-native"})
172
+ SendMessage(type: "message", recipient: "${leadName}", content: "fallback 필요: ${agentName} — claude-native 역할은 Claude Agent로 위임 필요", summary: "task ${taskId} fallback")
173
+
174
+ Step 3 TaskUpdate + SendMessage 즉시 종료. 추가 도구 호출 금지.`;
175
+ }
176
+
177
+ /**
178
+ * v3 하이브리드 래퍼 프롬프트 생성
179
+ * psmux pane 기반 비동기 실행 + polling 패턴.
180
+ * Agent가 idle 상태를 유지하여 인터럽트 수신이 가능하다.
181
+ *
182
+ * @param {'codex'|'gemini'} cli CLI 타입
183
+ * @param {object} opts
184
+ * @param {string} opts.subtask — 서브태스크 설명
185
+ * @param {string} [opts.role] 역할
186
+ * @param {string} [opts.teamName] 이름
187
+ * @param {string} [opts.taskId] — Hub task ID
188
+ * @param {string} [opts.agentName] 워커 표시 이름
189
+ * @param {string} [opts.leadName] 리드 수신자 이름
190
+ * @param {string} [opts.mcp_profile] — MCP 프로필
191
+ * @param {number} [opts.workerIndex] — 검색 힌트 회전에 사용할 워커 인덱스(1-based)
192
+ * @param {string} [opts.searchTool] — 전용 검색 도구 힌트(brave-search|tavily|exa)
193
+ * @param {string} [opts.sessionName] psmux 세션 이름
194
+ * @param {string} [opts.pipelinePhase] — 파이프라인 단계
195
+ * @param {string} [opts.psmuxPath] — psmux.mjs 경로
196
+ * @returns {string} 하이브리드 래퍼 프롬프트
197
+ */
198
+ export function buildHybridWrapperPrompt(cli, opts = {}) {
199
+ const {
200
+ subtask,
201
+ role = "executor",
202
+ teamName = "tfx-multi",
203
+ taskId = "",
204
+ agentName = "",
205
+ leadName = "team-lead",
206
+ mcp_profile = "auto",
207
+ workerIndex,
208
+ searchTool = "",
209
+ sessionName = teamName,
210
+ pipelinePhase = "",
211
+ psmuxPath = "hub/team/psmux.mjs",
212
+ } = opts;
213
+
214
+ const escaped = subtask.replace(/'/g, "'\\''");
215
+ const pipelineHint = pipelinePhase ? `\n파이프라인 단계: ${pipelinePhase}` : "";
216
+ const taskIdRef = taskId ? `taskId: "${taskId}"` : "";
217
+ const taskIdArg = taskIdRef ? `${taskIdRef}, ` : "";
218
+ const routeEnvPrefix = buildRouteEnvPrefix(agentName, workerIndex, searchTool);
219
+
220
+ const routeCmd = `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}`;
221
+
222
+ return `하이브리드 psmux 워커 프로토콜:
223
+
224
+ 1. TaskUpdate(${taskIdArg}status: in_progress) + SendMessage(to: ${leadName}, "작업 시작: ${agentName}")
225
+
226
+ 2. pane 생성 (비동기 실행):
227
+ Bash: node ${psmuxPath} spawn --session "${sessionName}" --name "${agentName}" --cmd "${routeCmd}"
228
+
229
+ 3. 폴링 루프 (10초 간격, idle 유지 → 인터럽트 수신 가능):
230
+ Bash: node ${psmuxPath} status --session "${sessionName}" --name "${agentName}"
231
+ - status: "running" → 10초 대기 후 재확인
232
+ - status: "exited" → 5단계로
233
+
234
+ 4. 인터럽트 수신 시:
235
+ Bash: node ${psmuxPath} kill --session "${sessionName}" --name "${agentName}"
236
+ → SendMessage(to: ${leadName}, "인터럽트 수신, 방향 전환")
237
+ → 새 지시에 따라 2단계부터 재실행
238
+
239
+ 5. 완료 시:
240
+ Bash: node ${psmuxPath} output --session "${sessionName}" --name "${agentName}" --lines 100
241
+ → 결과를 TaskUpdate + SendMessage로 보고
242
+ ${pipelineHint}
243
+ [HARD CONSTRAINT] 너는 Bash, TaskUpdate, TaskGet, TaskList, SendMessage만 사용할 수 있다.
244
+ Read, Edit, Write, Grep, Glob, Agent, WebSearch, WebFetch 등 다른 모든 도구 사용을 금지한다.
245
+ 코드를 직접 읽거나 수정하면 안 된다. 반드시 아래 Bash 명령(tfx-route.sh)을 통해 Codex/Gemini에 위임하라.
246
+ 이 규칙을 위반하면 작업 실패로 간주한다.
247
+
248
+ gemini/codex를 직접 호출하지 마라. psmux spawn이 tfx-route.sh를 통해 실행한다.
249
+ 프롬프트를 파일로 저장하지 마라. psmux spawn --cmd 인자로 전달된다.
250
+
251
+ 성공 → TaskUpdate(${taskIdArg}status: completed, metadata: {result: "success"}) + SendMessage(to: ${leadName}).
252
+ 실패 → TaskUpdate(${taskIdArg}status: completed, metadata: {result: "failed", error: "에러 요약"}) + SendMessage(to: ${leadName}).
253
+
254
+ 중요: TaskUpdate의 status는 "completed"만 사용. "failed"는 API 미지원.
255
+ 실패 여부는 metadata.result로 구분. pane 실패 시에도 반드시 TaskUpdate + SendMessage 후 종료.`;
256
+ }
257
+
258
+ /**
259
+ * 팀 이름 생성 (타임스탬프 기반)
260
+ * @returns {string}
261
+ */
262
+ export function generateTeamName() {
263
+ const ts = Date.now().toString(36).slice(-4);
264
+ const rand = Math.random().toString(36).slice(2, 6);
265
+ return `tfx-${ts}${rand}`;
266
+ }