triflux 3.2.0-dev.8 → 3.3.0-dev.1

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.
Files changed (52) hide show
  1. package/bin/triflux.mjs +1296 -1055
  2. package/hooks/hooks.json +17 -0
  3. package/hooks/keyword-rules.json +20 -4
  4. package/hooks/pipeline-stop.mjs +54 -0
  5. package/hub/bridge.mjs +517 -318
  6. package/hub/hitl.mjs +45 -31
  7. package/hub/pipe.mjs +457 -0
  8. package/hub/pipeline/index.mjs +121 -0
  9. package/hub/pipeline/state.mjs +164 -0
  10. package/hub/pipeline/transitions.mjs +114 -0
  11. package/hub/router.mjs +422 -161
  12. package/hub/schema.sql +14 -0
  13. package/hub/server.mjs +499 -424
  14. package/hub/store.mjs +388 -314
  15. package/hub/team/cli-team-common.mjs +348 -0
  16. package/hub/team/cli-team-control.mjs +393 -0
  17. package/hub/team/cli-team-start.mjs +516 -0
  18. package/hub/team/cli-team-status.mjs +269 -0
  19. package/hub/team/cli.mjs +75 -1475
  20. package/hub/team/dashboard.mjs +1 -9
  21. package/hub/team/native.mjs +190 -130
  22. package/hub/team/nativeProxy.mjs +165 -78
  23. package/hub/team/orchestrator.mjs +15 -20
  24. package/hub/team/pane.mjs +137 -103
  25. package/hub/team/psmux.mjs +506 -0
  26. package/hub/team/session.mjs +393 -330
  27. package/hub/team/shared.mjs +13 -0
  28. package/hub/team/staleState.mjs +299 -0
  29. package/hub/tools.mjs +105 -31
  30. package/hub/workers/claude-worker.mjs +446 -0
  31. package/hub/workers/codex-mcp.mjs +414 -0
  32. package/hub/workers/factory.mjs +18 -0
  33. package/hub/workers/gemini-worker.mjs +349 -0
  34. package/hub/workers/interface.mjs +41 -0
  35. package/hud/hud-qos-status.mjs +1790 -1788
  36. package/package.json +4 -1
  37. package/scripts/__tests__/keyword-detector.test.mjs +8 -8
  38. package/scripts/keyword-detector.mjs +15 -0
  39. package/scripts/lib/keyword-rules.mjs +4 -1
  40. package/scripts/preflight-cache.mjs +72 -0
  41. package/scripts/psmux-steering-prototype.sh +368 -0
  42. package/scripts/setup.mjs +136 -71
  43. package/scripts/tfx-route-worker.mjs +161 -0
  44. package/scripts/tfx-route.sh +485 -91
  45. package/skills/tfx-auto/SKILL.md +90 -564
  46. package/skills/tfx-auto-codex/SKILL.md +1 -3
  47. package/skills/tfx-codex/SKILL.md +1 -4
  48. package/skills/tfx-doctor/SKILL.md +1 -0
  49. package/skills/tfx-gemini/SKILL.md +1 -4
  50. package/skills/tfx-multi/SKILL.md +378 -0
  51. package/skills/tfx-setup/SKILL.md +1 -4
  52. package/skills/tfx-team/SKILL.md +0 -304
@@ -6,15 +6,7 @@
6
6
  // node hub/team/dashboard.mjs --session <세션이름> [--interval 2]
7
7
  // node hub/team/dashboard.mjs --team <팀이름> [--interval 2]
8
8
  import { get } from "node:http";
9
-
10
- // ── 색상 ──
11
- const AMBER = "\x1b[38;5;214m";
12
- const GREEN = "\x1b[38;5;82m";
13
- const RED = "\x1b[38;5;196m";
14
- const GRAY = "\x1b[38;5;245m";
15
- const DIM = "\x1b[2m";
16
- const BOLD = "\x1b[1m";
17
- const RESET = "\x1b[0m";
9
+ import { AMBER, GREEN, RED, GRAY, DIM, BOLD, RESET } from "./shared.mjs";
18
10
 
19
11
  /**
20
12
  * HTTP GET JSON
@@ -1,130 +1,190 @@
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 team --native)에서
7
- // 팀 설정을 프로그래밍적으로 생성할 때 사용한다.
8
-
9
- const ROUTE_SCRIPT = "~/.claude/scripts/tfx-route.sh";
10
-
11
- /**
12
- * CLI 타입별 teammate 프롬프트 생성
13
- * @param {'codex'|'gemini'|'claude'} cli — CLI 타입
14
- * @param {object} opts
15
- * @param {string} opts.subtask 서브태스크 설명
16
- * @param {string} [opts.role] — 역할 (executor, designer, reviewer 등)
17
- * @param {string} [opts.teamName] — 팀 이름
18
- * @returns {string} teammate 프롬프트
19
- */
20
- export function buildTeammatePrompt(cli, opts = {}) {
21
- const { subtask, role = "executor", teamName = "tfx-team" } = opts;
22
-
23
- if (cli === "claude") {
24
- return `너는 ${teamName}의 Claude 워커이다.
25
-
26
- [작업] ${subtask}
27
-
28
- [실행]
29
- 1. TaskList에서 pending 작업을 확인하고 claim (TaskUpdate: in_progress)
30
- 2. Glob, Grep, Read, Bash 도구로 직접 수행
31
- 3. 완료 TaskUpdate(status: completed) + SendMessage로 리드에게 보고
32
- 4. 추가 작업이 있으면 반복
33
-
34
- 에러 TaskUpdate(status: failed) + SendMessage로 보고.`;
35
- }
36
-
37
- const label = cli === "codex" ? "Codex" : "Gemini";
38
- const escaped = subtask.replace(/'/g, "'\\''");
39
-
40
- return `너는 ${teamName}의 ${label} 워커이다.
41
-
42
- [작업] ${subtask}
43
-
44
- [실행]
45
- 1. TaskList에서 pending 작업을 확인하고 claim (TaskUpdate: in_progress)
46
- 2. Bash("bash ${ROUTE_SCRIPT} ${role} '${escaped}' auto")로 실행
47
- 3. 결과 확인 TaskUpdate(status: completed) + SendMessage로 리드에게 보고
48
- 4. 추가 pending 작업이 있으면 반복
49
-
50
- [규칙]
51
- - 실제 구현은 ${label} CLI가 수행 너는 실행+보고 역할
52
- - 에러 TaskUpdate(status: failed) + SendMessage로 보고`;
53
- }
54
-
55
- /**
56
- * teammate 이름 생성
57
- * @param {'codex'|'gemini'|'claude'} cli
58
- * @param {number} index0-based
59
- * @returns {string}
60
- */
61
- export function buildTeammateName(cli, index) {
62
- return `${cli}-worker-${index + 1}`;
63
- }
64
-
65
- /**
66
- * 트리아지 결과에서 팀 멤버 설정 생성
67
- * @param {string} teamName — 팀 이름
68
- * @param {Array<{cli: string, subtask: string, role?: string}>} assignments
69
- * @returns {{ name: string, members: Array<{name: string, cli: string, prompt: string}> }}
70
- */
71
- export function buildTeamConfig(teamName, assignments) {
72
- return {
73
- name: teamName,
74
- members: assignments.map((a, i) => ({
75
- name: buildTeammateName(a.cli, i),
76
- cli: a.cli,
77
- prompt: buildTeammatePrompt(a.cli, {
78
- subtask: a.subtask,
79
- role: a.role || "executor",
80
- teamName,
81
- }),
82
- })),
83
- };
84
- }
85
-
86
- /**
87
- * v2.2 슬림 래퍼 프롬프트 생성
88
- * Agent spawn으로 네비게이션에 등록하되, 실제 작업은 tfx-route.sh가 수행.
89
- * 프롬프트 ~100 토큰 목표 (v2의 ~500 대비 80% 감소).
90
- *
91
- * @param {'codex'|'gemini'} cli CLI 타입
92
- * @param {object} opts
93
- * @param {string} opts.subtask — 서브태스크 설명
94
- * @param {string} [opts.role] 역할 (executor, designer, reviewer 등)
95
- * @param {string} [opts.teamName] 팀 이름
96
- * @param {string} [opts.taskId] — Hub task ID
97
- * @param {string} [opts.agentName] 워커 표시 이름
98
- * @param {string} [opts.leadName] — 리드 수신자 이름
99
- * @param {string} [opts.mcp_profile] MCP 프로필
100
- * @returns {string} 슬림 래퍼 프롬프트
101
- */
102
- export function buildSlimWrapperPrompt(cli, opts = {}) {
103
- const {
104
- subtask,
105
- role = "executor",
106
- teamName = "tfx-team",
107
- taskId = "",
108
- agentName = "",
109
- leadName = "team-lead",
110
- mcp_profile = "auto",
111
- } = opts;
112
-
113
- // 이스케이프
114
- const escaped = subtask.replace(/'/g, "'\\''");
115
-
116
- return `Bash 1회 실행 종료.
117
-
118
- 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}
119
-
120
- 완료 TaskUpdate(status: completed) + SendMessage(to: ${leadName}).
121
- 실패 TaskUpdate(status: failed) + SendMessage(to: ${leadName}).`;
122
- }
123
-
124
- /**
125
- * 이름 생성 (타임스탬프 기반)
126
- * @returns {string}
127
- */
128
- export function generateTeamName() {
129
- return `tfx-${Date.now().toString(36).slice(-6)}`;
130
- }
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
+ gemini/codex를 직접 호출하지 마라. 반드시 tfx-route.sh를 거쳐야 한다.
95
+ 프롬프트를 파일로 저장하지 마라. tfx-route.sh가 인자로 받는다.
96
+
97
+ 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})
98
+
99
+ 성공 TaskUpdate(${taskIdRef ? `${taskIdRef}, ` : ""}status: completed, metadata: {result: "success"}) + SendMessage(to: ${leadName}).
100
+ 실패 TaskUpdate(${taskIdRef ? `${taskIdRef}, ` : ""}status: completed, metadata: {result: "failed", error: "에러 요약"}) + SendMessage(to: ${leadName}).
101
+
102
+ 중요: TaskUpdate의 status는 "completed"만 사용. "failed"는 API 미지원.
103
+ 실패 여부는 metadata.result로 구분. Bash 실패 시에도 반드시 TaskUpdate + SendMessage 후 종료.`;
104
+ }
105
+
106
+ /**
107
+ * v3 하이브리드 래퍼 프롬프트 생성
108
+ * psmux pane 기반 비동기 실행 + polling 패턴.
109
+ * Agent가 idle 상태를 유지하여 인터럽트 수신이 가능하다.
110
+ *
111
+ * @param {'codex'|'gemini'} cli — CLI 타입
112
+ * @param {object} opts
113
+ * @param {string} opts.subtask — 서브태스크 설명
114
+ * @param {string} [opts.role] — 역할
115
+ * @param {string} [opts.teamName] — 팀 이름
116
+ * @param {string} [opts.taskId] — Hub task ID
117
+ * @param {string} [opts.agentName] — 워커 표시 이름
118
+ * @param {string} [opts.leadName] 리드 수신자 이름
119
+ * @param {string} [opts.mcp_profile] — MCP 프로필
120
+ * @param {number} [opts.workerIndex] — 검색 힌트 회전에 사용할 워커 인덱스(1-based)
121
+ * @param {string} [opts.searchTool] — 전용 검색 도구 힌트(brave-search|tavily|exa)
122
+ * @param {string} [opts.sessionName] — psmux 세션 이름
123
+ * @param {string} [opts.pipelinePhase] — 파이프라인 단계
124
+ * @param {string} [opts.psmuxPath] — psmux.mjs 경로
125
+ * @returns {string} 하이브리드 래퍼 프롬프트
126
+ */
127
+ export function buildHybridWrapperPrompt(cli, opts = {}) {
128
+ const {
129
+ subtask,
130
+ role = "executor",
131
+ teamName = "tfx-multi",
132
+ taskId = "",
133
+ agentName = "",
134
+ leadName = "team-lead",
135
+ mcp_profile = "auto",
136
+ workerIndex,
137
+ searchTool = "",
138
+ sessionName = teamName,
139
+ pipelinePhase = "",
140
+ psmuxPath = "hub/team/psmux.mjs",
141
+ } = opts;
142
+
143
+ const escaped = subtask.replace(/'/g, "'\\''");
144
+ const pipelineHint = pipelinePhase ? `\n파이프라인 단계: ${pipelinePhase}` : "";
145
+ const taskIdRef = taskId ? `taskId: "${taskId}"` : "";
146
+ const taskIdArg = taskIdRef ? `${taskIdRef}, ` : "";
147
+ const routeEnvPrefix = buildRouteEnvPrefix(agentName, workerIndex, searchTool);
148
+
149
+ 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}`;
150
+
151
+ return `하이브리드 psmux 워커 프로토콜:
152
+
153
+ 1. TaskUpdate(${taskIdArg}status: in_progress) + SendMessage(to: ${leadName}, "작업 시작: ${agentName}")
154
+
155
+ 2. pane 생성 (비동기 실행):
156
+ Bash: node ${psmuxPath} spawn --session "${sessionName}" --name "${agentName}" --cmd "${routeCmd}"
157
+
158
+ 3. 폴링 루프 (10초 간격, idle 유지 → 인터럽트 수신 가능):
159
+ Bash: node ${psmuxPath} status --session "${sessionName}" --name "${agentName}"
160
+ - status: "running" → 10초 대기 후 재확인
161
+ - status: "exited" → 5단계로
162
+
163
+ 4. 인터럽트 수신 시:
164
+ Bash: node ${psmuxPath} kill --session "${sessionName}" --name "${agentName}"
165
+ → SendMessage(to: ${leadName}, "인터럽트 수신, 방향 전환")
166
+ → 새 지시에 따라 2단계부터 재실행
167
+
168
+ 5. 완료 시:
169
+ Bash: node ${psmuxPath} output --session "${sessionName}" --name "${agentName}" --lines 100
170
+ → 결과를 TaskUpdate + SendMessage로 보고
171
+ ${pipelineHint}
172
+ gemini/codex를 직접 호출하지 마라. psmux spawn이 tfx-route.sh를 통해 실행한다.
173
+ 프롬프트를 파일로 저장하지 마라. psmux spawn --cmd 인자로 전달된다.
174
+
175
+ 성공 → TaskUpdate(${taskIdArg}status: completed, metadata: {result: "success"}) + SendMessage(to: ${leadName}).
176
+ 실패 → TaskUpdate(${taskIdArg}status: completed, metadata: {result: "failed", error: "에러 요약"}) + SendMessage(to: ${leadName}).
177
+
178
+ 중요: TaskUpdate의 status는 "completed"만 사용. "failed"는 API 미지원.
179
+ 실패 여부는 metadata.result로 구분. pane 실패 시에도 반드시 TaskUpdate + SendMessage 후 종료.`;
180
+ }
181
+
182
+ /**
183
+ * 팀 이름 생성 (타임스탬프 기반)
184
+ * @returns {string}
185
+ */
186
+ export function generateTeamName() {
187
+ const ts = Date.now().toString(36).slice(-4);
188
+ const rand = Math.random().toString(36).slice(2, 6);
189
+ return `tfx-${ts}${rand}`;
190
+ }