triflux 7.1.4 → 7.2.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.
- package/.claude-plugin/marketplace.json +31 -31
- package/.claude-plugin/plugin.json +22 -23
- package/bin/triflux.mjs +18 -5
- package/hooks/keyword-rules.json +393 -361
- package/hub/bridge.mjs +799 -786
- package/hub/delegator/contracts.mjs +37 -38
- package/hub/delegator/schema/delegator-tools.schema.json +250 -250
- package/hub/delegator/service.mjs +307 -302
- package/hub/intent.mjs +108 -11
- package/hub/lib/process-utils.mjs +20 -0
- package/hub/pipe.mjs +589 -589
- package/hub/pipeline/gates/confidence.mjs +1 -1
- package/hub/pipeline/gates/selfcheck.mjs +2 -4
- package/hub/pipeline/state.mjs +191 -187
- package/hub/pipeline/transitions.mjs +124 -120
- package/hub/public/dashboard.html +355 -349
- package/hub/quality/deslop.mjs +5 -3
- package/hub/reflexion.mjs +5 -1
- package/hub/research.mjs +6 -1
- package/hub/router.mjs +791 -782
- package/hub/server.mjs +893 -822
- package/hub/store.mjs +807 -778
- package/hub/team/agent-map.json +10 -0
- package/hub/team/ansi.mjs +3 -4
- package/hub/team/cli/commands/control.mjs +43 -43
- package/hub/team/cli/commands/interrupt.mjs +36 -36
- package/hub/team/cli/commands/kill.mjs +3 -3
- package/hub/team/cli/commands/send.mjs +37 -37
- package/hub/team/cli/commands/start/index.mjs +18 -8
- package/hub/team/cli/commands/start/parse-args.mjs +3 -1
- package/hub/team/cli/commands/start/start-headless.mjs +4 -1
- package/hub/team/cli/commands/status.mjs +87 -87
- package/hub/team/cli/commands/stop.mjs +1 -1
- package/hub/team/cli/commands/task.mjs +1 -1
- package/hub/team/cli/index.mjs +41 -39
- package/hub/team/cli/manifest.mjs +29 -28
- package/hub/team/cli/services/hub-client.mjs +37 -0
- package/hub/team/cli/services/state-store.mjs +26 -12
- package/hub/team/dashboard.mjs +11 -4
- package/hub/team/handoff.mjs +12 -0
- package/hub/team/headless.mjs +202 -200
- package/hub/team/native-supervisor.mjs +386 -346
- package/hub/team/nativeProxy.mjs +680 -692
- package/hub/team/staleState.mjs +361 -369
- package/hub/team/tui-viewer.mjs +27 -3
- package/hub/team/tui.mjs +1 -0
- package/hub/token-mode.mjs +114 -24
- package/hub/workers/delegator-mcp.mjs +1059 -1057
- package/hud/colors.mjs +88 -0
- package/hud/constants.mjs +78 -0
- package/hud/hud-qos-status.mjs +206 -1872
- package/hud/providers/claude.mjs +309 -0
- package/hud/providers/codex.mjs +151 -0
- package/hud/providers/gemini.mjs +320 -0
- package/hud/renderers.mjs +424 -0
- package/hud/terminal.mjs +140 -0
- package/hud/utils.mjs +271 -0
- package/package.json +1 -2
- package/scripts/__tests__/keyword-detector.test.mjs +234 -234
- package/scripts/headless-guard-fast.sh +21 -0
- package/scripts/headless-guard.mjs +26 -6
- package/scripts/lib/keyword-rules.mjs +166 -168
- package/scripts/setup.mjs +720 -690
- package/scripts/tfx-route-post.mjs +424 -424
- package/scripts/tfx-route.sh +1663 -1650
- package/scripts/tmp-cleanup.mjs +74 -0
- package/skills/tfx-auto/SKILL.md +279 -278
- package/skills/tfx-auto-codex/SKILL.md +98 -77
- package/skills/tfx-codex/SKILL.md +65 -65
- package/skills/tfx-gemini/SKILL.md +83 -82
- package/skills/tfx-hub/SKILL.md +205 -136
- package/skills/tfx-multi/SKILL.md +11 -5
- package/.mcp.json +0 -8
package/hub/team/tui-viewer.mjs
CHANGED
|
@@ -22,6 +22,14 @@ if (!SESSION) {
|
|
|
22
22
|
process.exit(1);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
// ── psmux 존재 확인 ──
|
|
26
|
+
try {
|
|
27
|
+
execFileSync("psmux", ["--version"], { encoding: "utf8", timeout: 2000 });
|
|
28
|
+
} catch {
|
|
29
|
+
process.stderr.write("ERROR: psmux not found or not executable. Install psmux before running tui-viewer.\n");
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
25
33
|
const tui = createLogDashboard({ refreshMs: 0 });
|
|
26
34
|
const startTime = Date.now();
|
|
27
35
|
tui.setStartTime(startTime);
|
|
@@ -38,6 +46,7 @@ function listPanes() {
|
|
|
38
46
|
return { index: parseInt(index, 10), title: title || "", pid };
|
|
39
47
|
});
|
|
40
48
|
} catch {
|
|
49
|
+
// psmux 미설치 또는 세션 없음 — 빈 목록 반환
|
|
41
50
|
return [];
|
|
42
51
|
}
|
|
43
52
|
}
|
|
@@ -49,6 +58,7 @@ function capturePane(paneIdx, lines = 5) {
|
|
|
49
58
|
"capture-pane", "-t", `${SESSION}:0.${paneIdx}`, "-p",
|
|
50
59
|
], { encoding: "utf8", timeout: 2000 }).trim().split("\n").slice(-lines).join("\n");
|
|
51
60
|
} catch {
|
|
61
|
+
// pane 캡처 실패 (pane 종료 또는 세션 소멸) — 빈 문자열 반환
|
|
52
62
|
return "";
|
|
53
63
|
}
|
|
54
64
|
}
|
|
@@ -62,6 +72,7 @@ function checkResultFile(paneName) {
|
|
|
62
72
|
if (content.trim().length === 0) return null;
|
|
63
73
|
return processHandoff(content, { exitCode: 0, resultFile });
|
|
64
74
|
} catch {
|
|
75
|
+
// result 파일 파싱 실패 — null 반환하여 진행 중으로 처리
|
|
65
76
|
return null;
|
|
66
77
|
}
|
|
67
78
|
}
|
|
@@ -69,9 +80,22 @@ function checkResultFile(paneName) {
|
|
|
69
80
|
// ── 메인 폴링 ──
|
|
70
81
|
const POLL_MS = 1000;
|
|
71
82
|
const workerState = new Map(); // paneName → { paneIdx, done }
|
|
83
|
+
let emptyPollCount = 0;
|
|
72
84
|
|
|
73
85
|
function poll() {
|
|
74
86
|
const panes = listPanes();
|
|
87
|
+
|
|
88
|
+
// 세션 사망 감지: 워커 pane이 없는 상태가 지속되면 자동 종료
|
|
89
|
+
if (!panes.some(p => p.index !== 0)) {
|
|
90
|
+
emptyPollCount++;
|
|
91
|
+
const threshold = workerState.size === 0 ? 15 : 10;
|
|
92
|
+
if (emptyPollCount >= threshold) {
|
|
93
|
+
tui.close(); clearInterval(timer); clearInterval(doneCheck); process.exit(0);
|
|
94
|
+
}
|
|
95
|
+
} else {
|
|
96
|
+
emptyPollCount = 0;
|
|
97
|
+
}
|
|
98
|
+
|
|
75
99
|
// pane 0 = 대시보드 (자기 자신), pane 1+ = 워커
|
|
76
100
|
for (const pane of panes) {
|
|
77
101
|
if (pane.index === 0) continue; // 자기 자신 건너뜀
|
|
@@ -111,7 +135,7 @@ function poll() {
|
|
|
111
135
|
// 완료 감지: (1) result 파일 존재, (2) 셸 프롬프트 복귀, (3) "tokens used" 텍스트
|
|
112
136
|
const resultFile = join(RESULT_DIR, `${SESSION}-${paneName}.txt`);
|
|
113
137
|
let resultSize = 0;
|
|
114
|
-
try { resultSize = statSync(resultFile).size; } catch {}
|
|
138
|
+
try { resultSize = statSync(resultFile).size; } catch { /* 파일 미존재 — size 0 유지 */ }
|
|
115
139
|
|
|
116
140
|
const shellReturned = /^(PS\s|>|\$)\s*/.test(lastLine) && lines.length > 2;
|
|
117
141
|
const tokensLine = lines.find(l => /tokens?\s+used/i.test(l));
|
|
@@ -152,5 +176,5 @@ const doneCheck = setInterval(() => {
|
|
|
152
176
|
}
|
|
153
177
|
}, 2000);
|
|
154
178
|
|
|
155
|
-
process.on("SIGINT", () => { tui.close(); clearInterval(timer); process.exit(0); });
|
|
156
|
-
setTimeout(() => { tui.close(); clearInterval(timer); process.exit(0); }, 10 * 60 * 1000);
|
|
179
|
+
process.on("SIGINT", () => { tui.close(); clearInterval(timer); clearInterval(doneCheck); process.exit(0); });
|
|
180
|
+
setTimeout(() => { tui.close(); clearInterval(timer); clearInterval(doneCheck); process.exit(0); }, 10 * 60 * 1000);
|
package/hub/team/tui.mjs
CHANGED
|
@@ -131,6 +131,7 @@ export function createLogDashboard(opts = {}) {
|
|
|
131
131
|
render,
|
|
132
132
|
getWorkers() { return new Map(workers); },
|
|
133
133
|
getFrameCount() { return frameCount; },
|
|
134
|
+
getPipelineState() { return { ...pipeline }; },
|
|
134
135
|
close() {
|
|
135
136
|
if (closed) return;
|
|
136
137
|
if (timer) clearInterval(timer);
|
package/hub/token-mode.mjs
CHANGED
|
@@ -20,39 +20,105 @@ export const COMPACT_RULES = [
|
|
|
20
20
|
{ from: ['pending', '대기'], to: '⏸', type: 'symbol' },
|
|
21
21
|
|
|
22
22
|
// ── 약어 (긴 매칭 우선 정렬) ──
|
|
23
|
-
{ from: ['configuration', '설정'], to: 'cfg',
|
|
24
|
-
{ from: ['implementation', '구현'], to: 'impl',
|
|
25
|
-
{ from: ['architecture', '아키텍처'], to: 'arch',
|
|
26
|
-
{ from: ['dependency', '의존성'], to: 'dep',
|
|
27
|
-
{ from: ['function', '함수'], to: 'fn',
|
|
28
|
-
{ from: ['parameter', '파라미터'], to: 'param',
|
|
29
|
-
{ from: ['repository', '저장소'], to: 'repo',
|
|
30
|
-
{ from: ['environment', '환경'], to: 'env',
|
|
31
|
-
{ from: ['variable', '변수'], to: 'var',
|
|
32
|
-
{ from: ['directory', '디렉토리'], to: 'dir',
|
|
23
|
+
{ from: ['configuration', '설정'], to: 'cfg', type: 'abbrev' },
|
|
24
|
+
{ from: ['implementation', '구현'], to: 'impl', type: 'abbrev' },
|
|
25
|
+
{ from: ['architecture', '아키텍처'], to: 'arch', type: 'abbrev' },
|
|
26
|
+
{ from: ['dependency', '의존성'], to: 'dep', type: 'abbrev' },
|
|
27
|
+
{ from: ['function', '함수'], to: 'fn', type: 'abbrev' },
|
|
28
|
+
{ from: ['parameter', '파라미터'], to: 'param', type: 'abbrev' },
|
|
29
|
+
{ from: ['repository', '저장소'], to: 'repo', type: 'abbrev' },
|
|
30
|
+
{ from: ['environment', '환경'], to: 'env', type: 'abbrev' },
|
|
31
|
+
{ from: ['variable', '변수'], to: 'var', type: 'abbrev' },
|
|
32
|
+
{ from: ['directory', '디렉토리'], to: 'dir', type: 'abbrev' },
|
|
33
|
+
|
|
34
|
+
// ── 한국어 동사/명령형 약어 ──
|
|
35
|
+
{ from: ['구현해'], to: 'impl', type: 'abbrev' },
|
|
36
|
+
{ from: ['확인해'], to: 'check', type: 'abbrev' },
|
|
37
|
+
{ from: ['수정해'], to: 'fix', type: 'abbrev' },
|
|
38
|
+
{ from: ['테스트'], to: 'test', type: 'abbrev' },
|
|
39
|
+
{ from: ['리뷰'], to: 'review', type: 'abbrev' },
|
|
40
|
+
{ from: ['분석'], to: 'analyze', type: 'abbrev' },
|
|
41
|
+
{ from: ['설계'], to: 'design', type: 'abbrev' },
|
|
42
|
+
{ from: ['문서화'], to: 'docs', type: 'abbrev' },
|
|
33
43
|
];
|
|
34
44
|
|
|
35
|
-
|
|
45
|
+
/** @type {Array<{ from: string[], to: string, type: 'symbol'|'abbrev' }>} */
|
|
46
|
+
export const REVIEW_RULES = [
|
|
47
|
+
{ from: ['looks good to me', 'lgtm'], to: '✓lgtm', type: 'abbrev' },
|
|
48
|
+
{ from: ['needs changes', '수정 필요'], to: '✗chg', type: 'abbrev' },
|
|
49
|
+
{ from: ['nitpick', '사소한'], to: 'nit', type: 'abbrev' },
|
|
50
|
+
{ from: ['blocking', '블로킹'], to: 'blk', type: 'abbrev' },
|
|
51
|
+
{ from: ['suggestion', '제안'], to: 'sug', type: 'abbrev' },
|
|
52
|
+
{ from: ['question', '질문'], to: 'q', type: 'abbrev' },
|
|
53
|
+
{ from: ['approved', '승인'], to: '✓apv', type: 'abbrev' },
|
|
54
|
+
{ from: ['request changes', '변경 요청'], to: '✗req', type: 'abbrev' },
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
/** @type {Array<{ from: string[], to: string, type: 'symbol'|'abbrev' }>} */
|
|
58
|
+
export const DESIGN_RULES = [
|
|
59
|
+
{ from: ['component', '컴포넌트'], to: 'cmp', type: 'abbrev' },
|
|
60
|
+
{ from: ['interface', '인터페이스'], to: 'iface', type: 'abbrev' },
|
|
61
|
+
{ from: ['abstraction', '추상화'], to: 'abs', type: 'abbrev' },
|
|
62
|
+
{ from: ['pattern', '패턴'], to: 'ptn', type: 'abbrev' },
|
|
63
|
+
{ from: ['dependency injection', '의존성 주입'], to: 'di', type: 'abbrev' },
|
|
64
|
+
{ from: ['single responsibility', '단일 책임'], to: 'srp', type: 'abbrev' },
|
|
65
|
+
{ from: ['open closed', '개방 폐쇄'], to: 'ocp', type: 'abbrev' },
|
|
66
|
+
{ from: ['inheritance', '상속'], to: 'inh', type: 'abbrev' },
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
/** @type {Array<{ from: string[], to: string, type: 'symbol'|'abbrev' }>} */
|
|
70
|
+
export const DOCS_RULES = [
|
|
71
|
+
{ from: ['description', '설명'], to: 'desc', type: 'abbrev' },
|
|
72
|
+
{ from: ['example', '예시'], to: 'ex', type: 'abbrev' },
|
|
73
|
+
{ from: ['reference', '참조'], to: 'ref', type: 'abbrev' },
|
|
74
|
+
{ from: ['introduction', '소개'], to: 'intro', type: 'abbrev' },
|
|
75
|
+
{ from: ['deprecated', '사용 중단'], to: 'dep', type: 'abbrev' },
|
|
76
|
+
{ from: ['optional', '선택적'], to: 'opt', type: 'abbrev' },
|
|
77
|
+
{ from: ['required', '필수'], to: 'req', type: 'abbrev' },
|
|
78
|
+
{ from: ['returns', '반환'], to: 'ret', type: 'abbrev' },
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
// ── 프로필 맵 ──
|
|
36
82
|
|
|
37
|
-
/** @type {Array<{
|
|
38
|
-
const
|
|
83
|
+
/** @type {Record<string, Array<{ from: string[], to: string, type: 'symbol'|'abbrev' }>>} */
|
|
84
|
+
const PROFILE_MAP = {
|
|
85
|
+
default: COMPACT_RULES,
|
|
86
|
+
review: [...COMPACT_RULES, ...REVIEW_RULES],
|
|
87
|
+
design: [...COMPACT_RULES, ...DESIGN_RULES],
|
|
88
|
+
docs: [...COMPACT_RULES, ...DOCS_RULES],
|
|
89
|
+
};
|
|
39
90
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
91
|
+
// ── 내부: 정렬된 치환 쌍 빌드 헬퍼 ──
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 규칙 배열로부터 패턴 쌍을 빌드하고 긴 패턴 우선 정렬
|
|
95
|
+
* @param {Array<{ from: string[], to: string }>} rules
|
|
96
|
+
* @returns {Array<{ pattern: string, to: string, len: number }>}
|
|
97
|
+
*/
|
|
98
|
+
function buildCompactPairs(rules) {
|
|
99
|
+
const pairs = [];
|
|
100
|
+
for (const rule of rules) {
|
|
101
|
+
for (const keyword of rule.from) {
|
|
102
|
+
pairs.push({ pattern: keyword, to: rule.to, len: keyword.length });
|
|
103
|
+
}
|
|
43
104
|
}
|
|
105
|
+
pairs.sort((a, b) => b.len - a.len);
|
|
106
|
+
return pairs;
|
|
44
107
|
}
|
|
45
|
-
// greedy: 긴 패턴 먼저
|
|
46
|
-
_compactPairs.sort((a, b) => b.len - a.len);
|
|
47
108
|
|
|
48
|
-
/**
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
109
|
+
/**
|
|
110
|
+
* 규칙 배열로부터 확장 쌍을 빌드
|
|
111
|
+
* @param {Array<{ from: string[], to: string }>} rules
|
|
112
|
+
* @returns {Array<{ symbol: string, restore: string }>}
|
|
113
|
+
*/
|
|
114
|
+
function buildExpandPairs(rules) {
|
|
115
|
+
return rules.map((rule) => ({ symbol: rule.to, restore: rule.from[0] }));
|
|
54
116
|
}
|
|
55
117
|
|
|
118
|
+
// 기본 프로필 쌍 (기존 동작 유지)
|
|
119
|
+
const _compactPairs = buildCompactPairs(COMPACT_RULES);
|
|
120
|
+
const _expandPairs = buildExpandPairs(COMPACT_RULES);
|
|
121
|
+
|
|
56
122
|
// ── 코드 블록 보호 유틸 ──
|
|
57
123
|
|
|
58
124
|
const CODE_BLOCK_RE = /```[\s\S]*?```/g;
|
|
@@ -114,6 +180,29 @@ export function compactify(text) {
|
|
|
114
180
|
});
|
|
115
181
|
}
|
|
116
182
|
|
|
183
|
+
/**
|
|
184
|
+
* 텍스트를 compact 모드로 변환 (도메인 프로필 선택 가능)
|
|
185
|
+
* @param {string} text
|
|
186
|
+
* @param {'default'|'review'|'design'|'docs'} [profile='default'] — 도메인 프로필
|
|
187
|
+
* @returns {string}
|
|
188
|
+
*/
|
|
189
|
+
export function applyCompactRules(text, profile = 'default') {
|
|
190
|
+
if (!text || typeof text !== 'string') return text ?? '';
|
|
191
|
+
|
|
192
|
+
const rules = PROFILE_MAP[profile] ?? COMPACT_RULES;
|
|
193
|
+
const pairs = buildCompactPairs(rules);
|
|
194
|
+
|
|
195
|
+
return withCodeProtection(text, (segment) => {
|
|
196
|
+
let result = segment;
|
|
197
|
+
for (const { pattern, to } of pairs) {
|
|
198
|
+
const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
199
|
+
const re = new RegExp(escaped, 'gi');
|
|
200
|
+
result = result.replace(re, to);
|
|
201
|
+
}
|
|
202
|
+
return result;
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
117
206
|
/**
|
|
118
207
|
* compact 텍스트를 원래 형태로 복원 (best-effort)
|
|
119
208
|
* @param {string} text
|
|
@@ -121,6 +210,7 @@ export function compactify(text) {
|
|
|
121
210
|
*/
|
|
122
211
|
export function expand(text) {
|
|
123
212
|
if (!text || typeof text !== 'string') return text ?? '';
|
|
213
|
+
_compactMode = false;
|
|
124
214
|
|
|
125
215
|
return withCodeProtection(text, (segment) => {
|
|
126
216
|
let result = segment;
|