triflux 7.1.4 → 7.2.2
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 +725 -690
- package/scripts/tfx-route-post.mjs +424 -424
- package/scripts/tfx-route.sh +1671 -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/intent.mjs
CHANGED
|
@@ -1,6 +1,63 @@
|
|
|
1
1
|
// hub/intent.mjs — Intent Classification Engine
|
|
2
2
|
// 사용자 요청의 "진짜 의도"를 분석 → 카테고리 분류 → 최적 에이전트/모델 자동 선택
|
|
3
3
|
|
|
4
|
+
import { execFileSync, execSync } from 'node:child_process';
|
|
5
|
+
import crypto from 'node:crypto';
|
|
6
|
+
|
|
7
|
+
/** 캐시 엔트리: { category, confidence, ts } */
|
|
8
|
+
const _intentCache = new Map();
|
|
9
|
+
const CACHE_TTL_MS = 5 * 60 * 1000; // 5분
|
|
10
|
+
|
|
11
|
+
/** codex 설치 여부 (프로세스당 1회 확인) */
|
|
12
|
+
let _codexAvailable = null;
|
|
13
|
+
|
|
14
|
+
function _isCodexAvailable() {
|
|
15
|
+
if (_codexAvailable !== null) return _codexAvailable;
|
|
16
|
+
try {
|
|
17
|
+
const cmd = process.platform === 'win32' ? 'where' : 'which';
|
|
18
|
+
execFileSync(cmd, ['codex'], { stdio: 'ignore' });
|
|
19
|
+
_codexAvailable = true;
|
|
20
|
+
} catch {
|
|
21
|
+
_codexAvailable = false;
|
|
22
|
+
}
|
|
23
|
+
return _codexAvailable;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function _promptHash(prompt) {
|
|
27
|
+
return crypto.createHash('md5').update(prompt).digest('hex');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function _getCached(hash) {
|
|
31
|
+
const entry = _intentCache.get(hash);
|
|
32
|
+
if (!entry) return null;
|
|
33
|
+
if (Date.now() - entry.ts > CACHE_TTL_MS) {
|
|
34
|
+
_intentCache.delete(hash);
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
return entry;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function _tryCodexClassify(prompt) {
|
|
41
|
+
try {
|
|
42
|
+
const raw = execSync(
|
|
43
|
+
`codex exec "Classify intent: ${prompt.replace(/"/g, '\\"')}. Reply JSON: {intent, confidence}"`,
|
|
44
|
+
{ timeout: 8000, encoding: 'utf8' }
|
|
45
|
+
);
|
|
46
|
+
// JSON 블록 추출 (응답에 다른 텍스트가 섞일 수 있음)
|
|
47
|
+
const match = raw.match(/\{[\s\S]*?\}/);
|
|
48
|
+
if (!match) return null;
|
|
49
|
+
const parsed = JSON.parse(match[0]);
|
|
50
|
+
const intent = typeof parsed.intent === 'string' ? parsed.intent : null;
|
|
51
|
+
const confidence = typeof parsed.confidence === 'number' ? parsed.confidence : null;
|
|
52
|
+
if (!intent || confidence === null) return null;
|
|
53
|
+
// intent가 알려진 카테고리여야 함
|
|
54
|
+
if (!INTENT_CATEGORIES[intent]) return null;
|
|
55
|
+
return { category: intent, confidence };
|
|
56
|
+
} catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
4
61
|
/** triflux 특화 의도 카테고리 (10개) */
|
|
5
62
|
export const INTENT_CATEGORIES = {
|
|
6
63
|
implement: { agent: 'executor', mcp: 'implement', effort: 'high' },
|
|
@@ -43,6 +100,7 @@ export function quickClassify(prompt) {
|
|
|
43
100
|
const lower = prompt.toLowerCase().trim();
|
|
44
101
|
let bestCategory = null;
|
|
45
102
|
let bestScore = 0;
|
|
103
|
+
let bestMatchCount = 0;
|
|
46
104
|
|
|
47
105
|
for (const { category, keywords, weight } of KEYWORD_PATTERNS) {
|
|
48
106
|
let matchCount = 0;
|
|
@@ -54,6 +112,7 @@ export function quickClassify(prompt) {
|
|
|
54
112
|
if (score > bestScore) {
|
|
55
113
|
bestScore = score;
|
|
56
114
|
bestCategory = category;
|
|
115
|
+
bestMatchCount = matchCount;
|
|
57
116
|
}
|
|
58
117
|
}
|
|
59
118
|
}
|
|
@@ -62,29 +121,67 @@ export function quickClassify(prompt) {
|
|
|
62
121
|
return { category: 'implement', confidence: 0.3 };
|
|
63
122
|
}
|
|
64
123
|
|
|
65
|
-
// 매칭 품질 기반 신뢰도 (0.5~0.95 범위)
|
|
66
|
-
const confidence = Math.min(0.95, 0.5 +
|
|
124
|
+
// 매칭 품질 기반 신뢰도 (0.5~0.95 범위) — matchCount 기준으로 정규화 (3개 매칭이면 최대)
|
|
125
|
+
const confidence = Math.min(0.95, 0.5 + (Math.min(bestMatchCount, 3) / 3) * 0.45);
|
|
67
126
|
return { category: bestCategory, confidence };
|
|
68
127
|
}
|
|
69
128
|
|
|
70
129
|
/**
|
|
71
130
|
* 전체 의도 분류 — routing 정보 포함
|
|
131
|
+
* Codex triage 경로: codex 설치 시 실행, confidence > 0.8이면 즉시 반환
|
|
132
|
+
* quickClassify가 고신뢰(>0.8)이면 Codex 건너뜀
|
|
133
|
+
* 결과는 md5 해시 기반 Map에 5분 TTL로 캐싱
|
|
72
134
|
* @param {string} prompt
|
|
73
135
|
* @returns {{ category: string, confidence: number, reasoning: string, routing: { agent: string, mcp: string|null, effort: string|null } }}
|
|
74
136
|
*/
|
|
75
137
|
export function classifyIntent(prompt) {
|
|
138
|
+
const hash = _promptHash(String(prompt ?? ''));
|
|
139
|
+
|
|
140
|
+
// 캐시 확인
|
|
141
|
+
const cached = _getCached(hash);
|
|
142
|
+
if (cached) {
|
|
143
|
+
const routing = INTENT_CATEGORIES[cached.category] || INTENT_CATEGORIES.implement;
|
|
144
|
+
return {
|
|
145
|
+
category: cached.category,
|
|
146
|
+
confidence: cached.confidence,
|
|
147
|
+
reasoning: `cache-hit: ${cached.category} (${cached.confidence.toFixed(2)})`,
|
|
148
|
+
routing: { agent: routing.agent, mcp: routing.mcp, effort: routing.effort },
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// quickClassify 먼저
|
|
76
153
|
const quick = quickClassify(prompt);
|
|
77
|
-
const routing = INTENT_CATEGORIES[quick.category] || INTENT_CATEGORIES.implement;
|
|
78
154
|
|
|
155
|
+
let category = quick.category;
|
|
156
|
+
let confidence = quick.confidence;
|
|
157
|
+
let reasoning;
|
|
158
|
+
|
|
159
|
+
// quickClassify가 고신뢰(>0.8)이면 Codex 건너뜀
|
|
160
|
+
if (quick.confidence > 0.8) {
|
|
161
|
+
reasoning = `keyword-match: ${category} (${confidence.toFixed(2)})`;
|
|
162
|
+
} else if (_isCodexAvailable()) {
|
|
163
|
+
// Codex triage
|
|
164
|
+
const codexResult = _tryCodexClassify(String(prompt ?? ''));
|
|
165
|
+
if (codexResult && codexResult.confidence > 0.8) {
|
|
166
|
+
category = codexResult.category;
|
|
167
|
+
confidence = codexResult.confidence;
|
|
168
|
+
reasoning = `codex-triage: ${category} (${confidence.toFixed(2)})`;
|
|
169
|
+
} else {
|
|
170
|
+
reasoning = `keyword-match(codex-fallback): ${category} (${confidence.toFixed(2)})`;
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
reasoning = `keyword-match: ${category} (${confidence.toFixed(2)})`;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// 캐시 저장
|
|
177
|
+
_intentCache.set(hash, { category, confidence, ts: Date.now() });
|
|
178
|
+
|
|
179
|
+
const routing = INTENT_CATEGORIES[category] || INTENT_CATEGORIES.implement;
|
|
79
180
|
return {
|
|
80
|
-
category
|
|
81
|
-
confidence
|
|
82
|
-
reasoning
|
|
83
|
-
routing: {
|
|
84
|
-
agent: routing.agent,
|
|
85
|
-
mcp: routing.mcp,
|
|
86
|
-
effort: routing.effort,
|
|
87
|
-
},
|
|
181
|
+
category,
|
|
182
|
+
confidence,
|
|
183
|
+
reasoning,
|
|
184
|
+
routing: { agent: routing.agent, mcp: routing.mcp, effort: routing.effort },
|
|
88
185
|
};
|
|
89
186
|
}
|
|
90
187
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// hub/lib/process-utils.mjs
|
|
2
|
+
// 프로세스 관련 공유 유틸리티
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 주어진 PID의 프로세스가 살아있는지 확인한다.
|
|
6
|
+
* EPERM: 프로세스는 존재하지만 signal 권한 없음 → alive
|
|
7
|
+
* ESRCH: 프로세스가 존재하지 않음 → dead
|
|
8
|
+
*/
|
|
9
|
+
export function isPidAlive(pid) {
|
|
10
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
process.kill(pid, 0);
|
|
14
|
+
return true;
|
|
15
|
+
} catch (e) {
|
|
16
|
+
if (e?.code === 'EPERM') return true;
|
|
17
|
+
if (e?.code === 'ESRCH') return false;
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|