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
|
@@ -1,28 +1,29 @@
|
|
|
1
|
-
export const TEAM_COMMANDS = [
|
|
2
|
-
{ name: "status", usage: "tfx multi status", desc: "현재 팀 상태" },
|
|
3
|
-
{ name: "debug", usage: "tfx multi debug [--lines 30]", desc: "강화 디버그 출력" },
|
|
4
|
-
{ name: "tasks", usage: "tfx multi tasks", desc: "공유 태스크 목록" },
|
|
5
|
-
{ name: "task", usage: "tfx multi task <pending|progress|done> <T1>", desc: "태스크 상태 갱신" },
|
|
6
|
-
{ name: "attach", usage: "tfx multi attach [--wt]", desc: "세션 재연결" },
|
|
7
|
-
{ name: "focus", usage: "tfx multi focus <lead|이름|번호> [--wt]", desc: "특정 팀메이트 포커스" },
|
|
8
|
-
{ name: "send", usage: "tfx multi send <lead|이름|번호> \"msg\"", desc: "팀메이트에 메시지 주입" },
|
|
9
|
-
{ name: "interrupt", usage: "tfx multi interrupt <대상>", desc: "팀메이트 인터럽트(C-c)" },
|
|
10
|
-
{ name: "control", usage: "tfx multi control <대상> <cmd> [사유]", desc: "리드 제어명령 전송" },
|
|
11
|
-
{ name: "stop", usage: "tfx multi stop", desc: "graceful 종료" },
|
|
12
|
-
{ name: "kill", usage: "tfx multi kill", desc: "모든 팀 세션 강제 종료" },
|
|
13
|
-
{ name: "list", usage: "tfx multi list", desc: "활성 세션 목록" },
|
|
14
|
-
{ name: "help", usage: "tfx multi help", desc: "도움말" },
|
|
15
|
-
]
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
["
|
|
20
|
-
]
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
1
|
+
export const TEAM_COMMANDS = [
|
|
2
|
+
{ name: "status", usage: "tfx multi status", desc: "현재 팀 상태" },
|
|
3
|
+
{ name: "debug", usage: "tfx multi debug [--lines 30]", desc: "강화 디버그 출력" },
|
|
4
|
+
{ name: "tasks", usage: "tfx multi tasks", desc: "공유 태스크 목록" },
|
|
5
|
+
{ name: "task", usage: "tfx multi task <pending|progress|done> <T1>", desc: "태스크 상태 갱신" },
|
|
6
|
+
{ name: "attach", usage: "tfx multi attach [--wt]", desc: "세션 재연결" },
|
|
7
|
+
{ name: "focus", usage: "tfx multi focus <lead|이름|번호> [--wt]", desc: "특정 팀메이트 포커스" },
|
|
8
|
+
{ name: "send", usage: "tfx multi send <lead|이름|번호> \"msg\"", desc: "팀메이트에 메시지 주입" },
|
|
9
|
+
{ name: "interrupt", usage: "tfx multi interrupt <대상>", desc: "팀메이트 인터럽트(C-c)" },
|
|
10
|
+
{ name: "control", usage: "tfx multi control <대상> <cmd> [사유]", desc: "리드 제어명령 전송" },
|
|
11
|
+
{ name: "stop", usage: "tfx multi stop", desc: "graceful 종료" },
|
|
12
|
+
{ name: "kill", usage: "tfx multi kill", desc: "모든 팀 세션 강제 종료" },
|
|
13
|
+
{ name: "list", usage: "tfx multi list", desc: "활성 세션 목록" },
|
|
14
|
+
{ name: "help", usage: "tfx multi help", desc: "도움말" },
|
|
15
|
+
{ name: "start", usage: "tfx multi start [options]", desc: "새 팀 세션 시작 (기본 커맨드)" },
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
export const TEAM_COMMAND_ALIASES = new Map([
|
|
19
|
+
["-h", "help"],
|
|
20
|
+
["--help", "help"],
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
export const TEAM_SUBCOMMANDS = new Set(TEAM_COMMANDS.map(({ name }) => name));
|
|
24
|
+
|
|
25
|
+
export function resolveTeamCommand(raw) {
|
|
26
|
+
if (typeof raw !== "string") return null;
|
|
27
|
+
const command = raw.toLowerCase();
|
|
28
|
+
return TEAM_SUBCOMMANDS.has(command) ? command : (TEAM_COMMAND_ALIASES.get(command) || null);
|
|
29
|
+
}
|
|
@@ -126,6 +126,43 @@ export async function startHubDaemon() {
|
|
|
126
126
|
return null;
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
/**
|
|
130
|
+
* Hub가 살아있는지 확인하고, 죽어있으면 재시작을 시도한다.
|
|
131
|
+
* exponential backoff: 1초, 2초, 4초
|
|
132
|
+
* 모든 재시작 실패 시 에러를 throw한다 (silent fail 아님).
|
|
133
|
+
* @param {number} [maxRetries=3]
|
|
134
|
+
* @returns {Promise<object>} Hub 정보
|
|
135
|
+
* @throws {Error} 모든 재시작 시도 실패 시
|
|
136
|
+
*/
|
|
137
|
+
export async function ensureHubAlive(maxRetries = 3) {
|
|
138
|
+
const hub = await getHubInfo();
|
|
139
|
+
if (hub && !hub.degraded) return hub;
|
|
140
|
+
|
|
141
|
+
let lastError = null;
|
|
142
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
143
|
+
try {
|
|
144
|
+
const restarted = await startHubDaemon();
|
|
145
|
+
if (restarted) {
|
|
146
|
+
// 재시작 후 연결 복구 확인
|
|
147
|
+
const recovered = await getHubInfo();
|
|
148
|
+
if (recovered) return recovered;
|
|
149
|
+
}
|
|
150
|
+
} catch (err) {
|
|
151
|
+
lastError = err;
|
|
152
|
+
}
|
|
153
|
+
// 다음 재시도 전 대기: 1초, 2초, 4초 (마지막 시도 후에는 대기 없음)
|
|
154
|
+
if (i < maxRetries - 1) {
|
|
155
|
+
const backoffMs = Math.pow(2, i) * 1000; // i=0: 1초, i=1: 2초, i=2: 4초
|
|
156
|
+
await new Promise((resolve) => setTimeout(resolve, backoffMs));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const error = new Error(`Hub 재시작 ${maxRetries}회 모두 실패${lastError ? `: ${lastError.message}` : ""}`);
|
|
161
|
+
error.code = "HUB_RESTART_FAILED";
|
|
162
|
+
error.cause = lastError;
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
165
|
+
|
|
129
166
|
export async function fetchHubTaskList(state) {
|
|
130
167
|
const hubBase = (state?.hubUrl || getDefaultHubUrl()).replace(/\/mcp$/, "");
|
|
131
168
|
const teamName = state?.native?.teamName || state?.sessionName || null;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { mkdirSync } from "node:fs";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { homedir } from "node:os";
|
|
@@ -11,24 +11,38 @@ export const TEAM_PROFILE = (() => {
|
|
|
11
11
|
return raw === "codex-team" ? "codex-team" : "team";
|
|
12
12
|
})();
|
|
13
13
|
|
|
14
|
-
const
|
|
15
|
-
HUB_PID_DIR,
|
|
16
|
-
TEAM_PROFILE === "codex-team" ? "team-state-codex-team.json" : "team-state.json",
|
|
17
|
-
);
|
|
14
|
+
export const SESSION_ID = process.env.CLAUDE_SESSION_ID || `s${Date.now()}`;
|
|
18
15
|
|
|
19
|
-
|
|
16
|
+
function getStatePath(sessionId) {
|
|
17
|
+
if (sessionId) return join(HUB_PID_DIR, `team-state-${sessionId}.json`);
|
|
18
|
+
return join(HUB_PID_DIR, TEAM_PROFILE === "codex-team" ? "team-state-codex-team.json" : "team-state.json");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function loadTeamState(sessionId) {
|
|
22
|
+
const resolvedId = sessionId || SESSION_ID;
|
|
23
|
+
const sessionPath = getStatePath(resolvedId);
|
|
24
|
+
try {
|
|
25
|
+
if (existsSync(sessionPath)) return JSON.parse(readFileSync(sessionPath, "utf8"));
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
// 세션별 파일 없으면 기존 team-state.json fallback
|
|
30
|
+
const legacyPath = getStatePath(null);
|
|
20
31
|
try {
|
|
21
|
-
return JSON.parse(readFileSync(
|
|
32
|
+
if (existsSync(legacyPath)) return JSON.parse(readFileSync(legacyPath, "utf8"));
|
|
22
33
|
} catch {
|
|
23
34
|
return null;
|
|
24
35
|
}
|
|
36
|
+
return null;
|
|
25
37
|
}
|
|
26
38
|
|
|
27
|
-
export function saveTeamState(state) {
|
|
28
|
-
|
|
29
|
-
|
|
39
|
+
export function saveTeamState(state, sessionId) {
|
|
40
|
+
const path = getStatePath(sessionId || state.sessionId || SESSION_ID);
|
|
41
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
42
|
+
writeFileSync(path, JSON.stringify({ ...state, profile: TEAM_PROFILE }, null, 2) + "\n");
|
|
30
43
|
}
|
|
31
44
|
|
|
32
|
-
export function clearTeamState() {
|
|
33
|
-
|
|
45
|
+
export function clearTeamState(sessionId) {
|
|
46
|
+
const path = getStatePath(sessionId || SESSION_ID);
|
|
47
|
+
if (existsSync(path)) unlinkSync(path);
|
|
34
48
|
}
|
package/hub/team/dashboard.mjs
CHANGED
|
@@ -218,14 +218,21 @@ export async function renderDashboard(sessionName, opts = {}) {
|
|
|
218
218
|
console.log(`${AMBER}└${GRAY}${border}${AMBER}┘${RESET}`);
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
-
/** team-state.json 로드 */
|
|
221
|
+
/** team-state.json 로드 (세션별 파일 우선, fallback: team-state.json) */
|
|
222
222
|
async function loadTeamState() {
|
|
223
223
|
try {
|
|
224
|
-
const { readFileSync } = await import("node:fs");
|
|
224
|
+
const { existsSync, readFileSync } = await import("node:fs");
|
|
225
225
|
const { join } = await import("node:path");
|
|
226
226
|
const { homedir } = await import("node:os");
|
|
227
|
-
const
|
|
228
|
-
|
|
227
|
+
const hubDir = join(homedir(), ".claude", "cache", "tfx-hub");
|
|
228
|
+
const sessionId = process.env.CLAUDE_SESSION_ID;
|
|
229
|
+
if (sessionId) {
|
|
230
|
+
const sessionPath = join(hubDir, `team-state-${sessionId}.json`);
|
|
231
|
+
if (existsSync(sessionPath)) return JSON.parse(readFileSync(sessionPath, "utf8"));
|
|
232
|
+
}
|
|
233
|
+
const legacyPath = join(hubDir, "team-state.json");
|
|
234
|
+
if (existsSync(legacyPath)) return JSON.parse(readFileSync(legacyPath, "utf8"));
|
|
235
|
+
return {};
|
|
229
236
|
} catch {
|
|
230
237
|
return {};
|
|
231
238
|
}
|
package/hub/team/handoff.mjs
CHANGED
|
@@ -41,6 +41,18 @@ Rules:
|
|
|
41
41
|
- Do not skip any required field
|
|
42
42
|
`.trim();
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* CLI 프롬프트 길이 제한을 고려한 축약 HANDOFF 지시
|
|
46
|
+
*/
|
|
47
|
+
export const HANDOFF_INSTRUCTION_SHORT =
|
|
48
|
+
`After completing, output this block at the end:
|
|
49
|
+
--- HANDOFF ---
|
|
50
|
+
status: ok | partial | failed
|
|
51
|
+
lead_action: accept | needs_read | retry | reassign
|
|
52
|
+
verdict: <one sentence>
|
|
53
|
+
files_changed: <comma-separated paths or "none">
|
|
54
|
+
confidence: high | medium | low`;
|
|
55
|
+
|
|
44
56
|
/**
|
|
45
57
|
* raw 텍스트에서 HANDOFF 블록을 파싱한다.
|
|
46
58
|
* @param {string} rawText
|