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
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"executor": "codex", "build-fixer": "codex", "debugger": "codex", "deep-executor": "codex",
|
|
3
|
+
"architect": "codex", "planner": "codex", "critic": "codex", "analyst": "codex",
|
|
4
|
+
"code-reviewer": "codex", "security-reviewer": "codex", "quality-reviewer": "codex",
|
|
5
|
+
"scientist": "codex", "scientist-deep": "codex", "document-specialist": "codex",
|
|
6
|
+
"spark": "codex",
|
|
7
|
+
"designer": "gemini", "writer": "gemini",
|
|
8
|
+
"explore": "claude", "verifier": "claude", "test-engineer": "claude", "qa-tester": "claude",
|
|
9
|
+
"codex": "codex", "gemini": "gemini", "claude": "claude"
|
|
10
|
+
}
|
package/hub/team/ansi.mjs
CHANGED
|
@@ -84,17 +84,16 @@ export function padRight(str, len) {
|
|
|
84
84
|
export function truncate(str, maxLen) {
|
|
85
85
|
const visible = stripAnsi(str);
|
|
86
86
|
if (visible.length <= maxLen) return str;
|
|
87
|
-
|
|
88
|
-
return str.slice(0, maxLen - 1) + "…";
|
|
87
|
+
return visible.slice(0, maxLen - 1) + "…";
|
|
89
88
|
}
|
|
90
89
|
|
|
91
90
|
export function stripAnsi(str) {
|
|
92
|
-
return str.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "");
|
|
91
|
+
return str.replace(/\x1b\[[0-9;]*[a-zA-Z]|\x1b\].*?(\x07|\x1b\\)/g, "");
|
|
93
92
|
}
|
|
94
93
|
|
|
95
94
|
// ── 진행률 바 ──
|
|
96
95
|
export function progressBar(ratio, width = 20) {
|
|
97
|
-
const filled = Math.round(ratio * width);
|
|
96
|
+
const filled = Math.max(0, Math.min(width, Math.round(ratio * width)));
|
|
98
97
|
const empty = width - filled;
|
|
99
98
|
return `${FG.accent}${"█".repeat(filled)}${FG.muted}${"░".repeat(empty)}${RESET}`;
|
|
100
99
|
}
|
|
@@ -1,43 +1,43 @@
|
|
|
1
|
-
import { injectPrompt, sendKeys } from "../../pane.mjs";
|
|
2
|
-
import { DIM, RESET, WHITE } from "../../shared.mjs";
|
|
3
|
-
import { publishLeadControl } from "../services/hub-client.mjs";
|
|
4
|
-
import { resolveMember } from "../services/member-selector.mjs";
|
|
5
|
-
import { nativeRequest } from "../services/native-control.mjs";
|
|
6
|
-
import { isNativeMode, isTeamAlive, isWtMode } from "../services/runtime-mode.mjs";
|
|
7
|
-
import { loadTeamState } from "../services/state-store.mjs";
|
|
8
|
-
import { ok, warn } from "../render.mjs";
|
|
9
|
-
|
|
10
|
-
export async function teamControl(args = []) {
|
|
11
|
-
const state = loadTeamState();
|
|
12
|
-
if (!state || !isTeamAlive(state)) {
|
|
13
|
-
console.log(`\n ${DIM}활성 팀 세션 없음${RESET}\n`);
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const member = resolveMember(state, args[0]);
|
|
18
|
-
const command = String(args[1] || "").toLowerCase();
|
|
19
|
-
const reason = args.slice(2).join(" ");
|
|
20
|
-
if (!member || !new Set(["interrupt", "stop", "pause", "resume"]).has(command)) {
|
|
21
|
-
console.log(`\n 사용법: ${WHITE}tfx multi control <lead|이름|번호> <interrupt|stop|pause|resume> [사유]${RESET}\n`);
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
if (isWtMode(state)) {
|
|
25
|
-
console.log(`\n
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
let directOk = false;
|
|
30
|
-
if (isNativeMode(state)) {
|
|
31
|
-
directOk = !!(await nativeRequest(state, "/control", { member: member.name, command, reason }))?.ok;
|
|
32
|
-
} else {
|
|
33
|
-
injectPrompt(member.pane, `[LEAD CONTROL] command=${command}${reason ? ` reason=${reason}` : ""}`);
|
|
34
|
-
if (command === "interrupt") sendKeys(member.pane, "C-c");
|
|
35
|
-
directOk = true;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const published = await publishLeadControl(state, member, command, reason);
|
|
39
|
-
if (directOk && published) ok(`${member.name} 제어 전송 (${command}, direct + hub)`);
|
|
40
|
-
else if (directOk) ok(`${member.name} 제어 전송 (${command}, direct only)`);
|
|
41
|
-
else warn(`${member.name} 제어 전송 실패 (${command})`);
|
|
42
|
-
console.log("");
|
|
43
|
-
}
|
|
1
|
+
import { injectPrompt, sendKeys } from "../../pane.mjs";
|
|
2
|
+
import { DIM, RESET, WHITE, YELLOW } from "../../shared.mjs";
|
|
3
|
+
import { publishLeadControl } from "../services/hub-client.mjs";
|
|
4
|
+
import { resolveMember } from "../services/member-selector.mjs";
|
|
5
|
+
import { nativeRequest } from "../services/native-control.mjs";
|
|
6
|
+
import { isNativeMode, isTeamAlive, isWtMode } from "../services/runtime-mode.mjs";
|
|
7
|
+
import { loadTeamState } from "../services/state-store.mjs";
|
|
8
|
+
import { ok, warn } from "../render.mjs";
|
|
9
|
+
|
|
10
|
+
export async function teamControl(args = []) {
|
|
11
|
+
const state = loadTeamState();
|
|
12
|
+
if (!state || !isTeamAlive(state)) {
|
|
13
|
+
console.log(`\n ${DIM}활성 팀 세션 없음${RESET}\n`);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const member = resolveMember(state, args[0]);
|
|
18
|
+
const command = String(args[1] || "").toLowerCase();
|
|
19
|
+
const reason = args.slice(2).join(" ");
|
|
20
|
+
if (!member || !new Set(["interrupt", "stop", "pause", "resume"]).has(command)) {
|
|
21
|
+
console.log(`\n 사용법: ${WHITE}tfx multi control <lead|이름|번호> <interrupt|stop|pause|resume> [사유]${RESET}\n`);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (isWtMode(state)) {
|
|
25
|
+
console.log(`\n ${YELLOW}⚠${RESET} wt 모드는 Hub direct/control 주입 경로가 비활성입니다.\n ${DIM}수동 제어: 해당 pane에서 직접 명령/인터럽트를 수행하세요.${RESET}\n`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let directOk = false;
|
|
30
|
+
if (isNativeMode(state)) {
|
|
31
|
+
directOk = !!(await nativeRequest(state, "/control", { member: member.name, command, reason }))?.ok;
|
|
32
|
+
} else {
|
|
33
|
+
injectPrompt(member.pane, `[LEAD CONTROL] command=${command}${reason ? ` reason=${reason}` : ""}`);
|
|
34
|
+
if (command === "interrupt") sendKeys(member.pane, "C-c");
|
|
35
|
+
directOk = true;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const published = await publishLeadControl(state, member, command, reason);
|
|
39
|
+
if (directOk && published) ok(`${member.name} 제어 전송 (${command}, direct + hub)`);
|
|
40
|
+
else if (directOk) ok(`${member.name} 제어 전송 (${command}, direct only)`);
|
|
41
|
+
else warn(`${member.name} 제어 전송 실패 (${command})`);
|
|
42
|
+
console.log("");
|
|
43
|
+
}
|
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
import { sendKeys } from "../../pane.mjs";
|
|
2
|
-
import { DIM, RESET, WHITE } from "../../shared.mjs";
|
|
3
|
-
import { resolveMember } from "../services/member-selector.mjs";
|
|
4
|
-
import { nativeRequest } from "../services/native-control.mjs";
|
|
5
|
-
import { isNativeMode, isTeamAlive, isWtMode } from "../services/runtime-mode.mjs";
|
|
6
|
-
import { loadTeamState } from "../services/state-store.mjs";
|
|
7
|
-
import { ok, warn } from "../render.mjs";
|
|
8
|
-
|
|
9
|
-
export async function teamInterrupt(args = []) {
|
|
10
|
-
const state = loadTeamState();
|
|
11
|
-
if (!state || !isTeamAlive(state)) {
|
|
12
|
-
console.log(`\n ${DIM}활성 팀 세션 없음${RESET}\n`);
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const member = resolveMember(state, args[0] || "lead");
|
|
17
|
-
if (!member) {
|
|
18
|
-
console.log(`\n 사용법: ${WHITE}tfx multi interrupt <lead|이름|번호>${RESET}\n`);
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
if (isWtMode(state)) {
|
|
22
|
-
console.log(`\n
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (isNativeMode(state)) {
|
|
27
|
-
const result = await nativeRequest(state, "/interrupt", { member: member.name });
|
|
28
|
-
(result?.ok ? ok : warn)(`${member.name} ${result?.ok ? "인터럽트 전송" : "인터럽트 실패"}`);
|
|
29
|
-
console.log("");
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
sendKeys(member.pane, "C-c");
|
|
34
|
-
ok(`${member.name} 인터럽트 전송`);
|
|
35
|
-
console.log("");
|
|
36
|
-
}
|
|
1
|
+
import { sendKeys } from "../../pane.mjs";
|
|
2
|
+
import { DIM, RESET, WHITE, YELLOW } from "../../shared.mjs";
|
|
3
|
+
import { resolveMember } from "../services/member-selector.mjs";
|
|
4
|
+
import { nativeRequest } from "../services/native-control.mjs";
|
|
5
|
+
import { isNativeMode, isTeamAlive, isWtMode } from "../services/runtime-mode.mjs";
|
|
6
|
+
import { loadTeamState } from "../services/state-store.mjs";
|
|
7
|
+
import { ok, warn } from "../render.mjs";
|
|
8
|
+
|
|
9
|
+
export async function teamInterrupt(args = []) {
|
|
10
|
+
const state = loadTeamState();
|
|
11
|
+
if (!state || !isTeamAlive(state)) {
|
|
12
|
+
console.log(`\n ${DIM}활성 팀 세션 없음${RESET}\n`);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const member = resolveMember(state, args[0] || "lead");
|
|
17
|
+
if (!member) {
|
|
18
|
+
console.log(`\n 사용법: ${WHITE}tfx multi interrupt <lead|이름|번호>${RESET}\n`);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (isWtMode(state)) {
|
|
22
|
+
console.log(`\n ${YELLOW}⚠${RESET} wt 모드에서는 pane stdin 주입이 지원되지 않아 interrupt를 자동 전송할 수 없습니다.\n ${DIM}수동으로 해당 pane에서 Ctrl+C를 입력하세요.${RESET}\n`);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (isNativeMode(state)) {
|
|
27
|
+
const result = await nativeRequest(state, "/interrupt", { member: member.name });
|
|
28
|
+
(result?.ok ? ok : warn)(`${member.name} ${result?.ok ? "인터럽트 전송" : "인터럽트 실패"}`);
|
|
29
|
+
console.log("");
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
sendKeys(member.pane, "C-c");
|
|
34
|
+
ok(`${member.name} 인터럽트 전송`);
|
|
35
|
+
console.log("");
|
|
36
|
+
}
|
|
@@ -10,14 +10,14 @@ export async function teamKill() {
|
|
|
10
10
|
if (state && isNativeMode(state) && isTeamAlive(state)) {
|
|
11
11
|
await nativeRequest(state, "/stop", {});
|
|
12
12
|
try { process.kill(state.native.supervisorPid, "SIGTERM"); } catch {}
|
|
13
|
-
clearTeamState();
|
|
13
|
+
clearTeamState(state.sessionId);
|
|
14
14
|
ok(`종료: ${state.sessionName}`);
|
|
15
15
|
console.log("");
|
|
16
16
|
return;
|
|
17
17
|
}
|
|
18
18
|
if (state && isWtMode(state)) {
|
|
19
19
|
const closed = closeWtSession({ layout: state?.wt?.layout || state?.layout || "1xN", paneCount: state?.wt?.paneCount ?? (state.members || []).length });
|
|
20
|
-
clearTeamState();
|
|
20
|
+
clearTeamState(state.sessionId);
|
|
21
21
|
ok(`종료: ${state.sessionName}${closed ? ` (${closed} panes closed)` : ""}`);
|
|
22
22
|
console.log("");
|
|
23
23
|
return;
|
|
@@ -32,6 +32,6 @@ export async function teamKill() {
|
|
|
32
32
|
killSession(session);
|
|
33
33
|
ok(`종료: ${session}`);
|
|
34
34
|
}
|
|
35
|
-
clearTeamState();
|
|
35
|
+
clearTeamState(state?.sessionId);
|
|
36
36
|
console.log("");
|
|
37
37
|
}
|
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
import { injectPrompt } from "../../pane.mjs";
|
|
2
|
-
import { DIM, RESET, WHITE } from "../../shared.mjs";
|
|
3
|
-
import { resolveMember } from "../services/member-selector.mjs";
|
|
4
|
-
import { nativeRequest } from "../services/native-control.mjs";
|
|
5
|
-
import { isNativeMode, isTeamAlive, isWtMode } from "../services/runtime-mode.mjs";
|
|
6
|
-
import { loadTeamState } from "../services/state-store.mjs";
|
|
7
|
-
import { ok, warn } from "../render.mjs";
|
|
8
|
-
|
|
9
|
-
export async function teamSend(args = []) {
|
|
10
|
-
const state = loadTeamState();
|
|
11
|
-
if (!state || !isTeamAlive(state)) {
|
|
12
|
-
console.log(`\n ${DIM}활성 팀 세션 없음${RESET}\n`);
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const member = resolveMember(state, args[0]);
|
|
17
|
-
const message = args.slice(1).join(" ");
|
|
18
|
-
if (!member || !message) {
|
|
19
|
-
console.log(`\n 사용법: ${WHITE}tfx multi send <lead|이름|번호> "메시지"${RESET}\n`);
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
if (isWtMode(state)) {
|
|
23
|
-
console.log(`\n
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (isNativeMode(state)) {
|
|
28
|
-
const result = await nativeRequest(state, "/send", { member: member.name, text: message });
|
|
29
|
-
(result?.ok ? ok : warn)(`${member.name}${result?.ok ? "에 메시지 주입 완료" : " 메시지 주입 실패"}`);
|
|
30
|
-
console.log("");
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
injectPrompt(member.pane, message);
|
|
35
|
-
ok(`${member.name}에 메시지 주입 완료`);
|
|
36
|
-
console.log("");
|
|
37
|
-
}
|
|
1
|
+
import { injectPrompt } from "../../pane.mjs";
|
|
2
|
+
import { DIM, RESET, WHITE, YELLOW } from "../../shared.mjs";
|
|
3
|
+
import { resolveMember } from "../services/member-selector.mjs";
|
|
4
|
+
import { nativeRequest } from "../services/native-control.mjs";
|
|
5
|
+
import { isNativeMode, isTeamAlive, isWtMode } from "../services/runtime-mode.mjs";
|
|
6
|
+
import { loadTeamState } from "../services/state-store.mjs";
|
|
7
|
+
import { ok, warn } from "../render.mjs";
|
|
8
|
+
|
|
9
|
+
export async function teamSend(args = []) {
|
|
10
|
+
const state = loadTeamState();
|
|
11
|
+
if (!state || !isTeamAlive(state)) {
|
|
12
|
+
console.log(`\n ${DIM}활성 팀 세션 없음${RESET}\n`);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const member = resolveMember(state, args[0]);
|
|
17
|
+
const message = args.slice(1).join(" ");
|
|
18
|
+
if (!member || !message) {
|
|
19
|
+
console.log(`\n 사용법: ${WHITE}tfx multi send <lead|이름|번호> "메시지"${RESET}\n`);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (isWtMode(state)) {
|
|
23
|
+
console.log(`\n ${YELLOW}⚠${RESET} wt 모드는 pane 프롬프트 자동 주입(send)이 지원되지 않습니다.\n ${DIM}수동 전달: 선택한 pane에 직접 붙여넣으세요.${RESET}\n`);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (isNativeMode(state)) {
|
|
28
|
+
const result = await nativeRequest(state, "/send", { member: member.name, text: message });
|
|
29
|
+
(result?.ok ? ok : warn)(`${member.name}${result?.ok ? "에 메시지 주입 완료" : " 메시지 주입 실패"}`);
|
|
30
|
+
console.log("");
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
injectPrompt(member.pane, message);
|
|
35
|
+
ok(`${member.name}에 메시지 주입 완료`);
|
|
36
|
+
console.log("");
|
|
37
|
+
}
|
|
@@ -44,13 +44,23 @@ export async function teamStart(args = []) {
|
|
|
44
44
|
if (!task) return printStartUsage();
|
|
45
45
|
|
|
46
46
|
console.log(`\n ${AMBER}${BOLD}⬡ tfx multi${RESET}\n`);
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
47
|
+
|
|
48
|
+
// P1b: 워커 수 계산 — 단일 워커 headless에는 Hub 불필요
|
|
49
|
+
const workerCount = assigns.length > 0 ? assigns.length : agents.length;
|
|
50
|
+
const needsHub = workerCount >= 2 || teammateMode !== "headless";
|
|
51
|
+
|
|
52
|
+
let hub = null;
|
|
53
|
+
if (needsHub) {
|
|
54
|
+
hub = await getHubInfo();
|
|
55
|
+
if (!hub) {
|
|
56
|
+
process.stdout.write(" Hub 시작 중...");
|
|
57
|
+
try { hub = await startHubDaemon(); } catch (error) { if (error?.code === "HUB_SERVER_MISSING") fail("hub/server.mjs 없음 — hub 모듈이 설치되지 않음"); }
|
|
58
|
+
console.log(` ${hub ? `${GREEN}✓${RESET}` : `${RED}✗${RESET}`}`);
|
|
59
|
+
if (!hub) warn("Hub 시작 실패 — 수동으로 실행: tfx hub start");
|
|
60
|
+
} else ok(`Hub: ${DIM}${hub.url}${RESET}`);
|
|
61
|
+
} else {
|
|
62
|
+
ok(`Hub: ${DIM}건너뜀 (단일 워커 headless)${RESET}`);
|
|
63
|
+
}
|
|
54
64
|
|
|
55
65
|
const sessionId = `tfx-multi-${Date.now().toString(36).slice(-4)}${Math.random().toString(36).slice(2, 6)}`;
|
|
56
66
|
const subtasks = decomposeTask(task, agents.length);
|
|
@@ -78,7 +88,7 @@ export async function teamStart(args = []) {
|
|
|
78
88
|
: await startMuxTeam({ sessionId, task, lead, agents, subtasks, layout, hubUrl, teammateMode: effectiveMode });
|
|
79
89
|
|
|
80
90
|
if (!state) return fail("in-process supervisor 시작 실패");
|
|
81
|
-
saveTeamState(state);
|
|
91
|
+
saveTeamState(state, sessionId);
|
|
82
92
|
if (typeof state.postSave === "function") state.postSave();
|
|
83
93
|
if (effectiveMode === "in-process") {
|
|
84
94
|
ok("네이티브 in-process 팀 시작 완료");
|
|
@@ -44,7 +44,9 @@ export function parseTeamArgs(args = []) {
|
|
|
44
44
|
timeoutSec = Number(args[++index]) || 300;
|
|
45
45
|
} else if (current === "--mcp-profile" && args[index + 1]) {
|
|
46
46
|
mcpProfile = args[++index].trim();
|
|
47
|
-
} else if (
|
|
47
|
+
} else if (current.startsWith("-")) {
|
|
48
|
+
console.warn(` ⚠ 미인식 플래그 무시: ${current}`);
|
|
49
|
+
} else {
|
|
48
50
|
taskParts.push(current);
|
|
49
51
|
}
|
|
50
52
|
}
|
|
@@ -73,6 +73,9 @@ export async function startHeadlessTeam({ sessionId, task, lead, agents, subtask
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
// dashboard 모드: tui-viewer가 최종 상태를 캡처할 시간 확보
|
|
77
|
+
if (dashboard) await new Promise(r => setTimeout(r, 2000));
|
|
78
|
+
|
|
76
79
|
// 세션 정리
|
|
77
80
|
handle.kill();
|
|
78
81
|
|
|
@@ -95,7 +98,7 @@ export async function startHeadlessTeam({ sessionId, task, lead, agents, subtask
|
|
|
95
98
|
tasks: buildTasks(assignments.map(a => a.prompt), members.filter((m) => m.role === "worker")),
|
|
96
99
|
postSave() {
|
|
97
100
|
// headless는 실행 완료 후 즉시 정리 — HUD에 잔존 방지
|
|
98
|
-
clearTeamState();
|
|
101
|
+
clearTeamState(sessionId);
|
|
99
102
|
console.log(`\n ${DIM}세션 정리 완료.${RESET}\n`);
|
|
100
103
|
},
|
|
101
104
|
};
|
|
@@ -1,87 +1,87 @@
|
|
|
1
|
-
import { AMBER, BOLD, DIM, GRAY, GREEN, RED, RESET } from "../../shared.mjs";
|
|
2
|
-
import { hasWindowsTerminalSession } from "../../session.mjs";
|
|
3
|
-
import { fetchHubTaskList, nativeGetStatus } from "../services/hub-client.mjs";
|
|
4
|
-
import { isNativeMode, isTeamAlive, isWtMode } from "../services/runtime-mode.mjs";
|
|
5
|
-
import { loadTeamState } from "../services/state-store.mjs";
|
|
6
|
-
import { formatCompletionSuffix } from "../render.mjs";
|
|
7
|
-
|
|
8
|
-
export async function teamStatus(args = []) {
|
|
9
|
-
const json = process.env.TFX_OUTPUT_JSON === "1" || args.includes("--json");
|
|
10
|
-
const state = loadTeamState();
|
|
11
|
-
if (!state) {
|
|
12
|
-
if (json) {
|
|
13
|
-
process.stdout.write(`${JSON.stringify({ status: "offline", sessionName: null, alive: false }, null, 2)}\n`);
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
console.log(`\n ${DIM}활성 팀 세션 없음${RESET}\n`);
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const alive = isTeamAlive(state);
|
|
21
|
-
const payload = {
|
|
22
|
-
status: alive ? "active" : "dead",
|
|
23
|
-
alive,
|
|
24
|
-
sessionName: state.sessionName,
|
|
25
|
-
teammateMode: state.teammateMode || "tmux",
|
|
26
|
-
lead: state.lead || "claude",
|
|
27
|
-
agents: state.agents || [],
|
|
28
|
-
startedAt: state.startedAt || null,
|
|
29
|
-
taskCount: (state.tasks || []).length,
|
|
30
|
-
members: (state.members || []).map((member) => ({
|
|
31
|
-
name: member.name,
|
|
32
|
-
cli: member.cli,
|
|
33
|
-
role: member.role,
|
|
34
|
-
pane: member.pane,
|
|
35
|
-
})),
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
if (isNativeMode(state) && alive) {
|
|
39
|
-
payload.nativeMembers = (await nativeGetStatus(state))?.data?.members || [];
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (alive) {
|
|
43
|
-
payload.hubTasks = await fetchHubTaskList(state);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (json) {
|
|
47
|
-
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
console.log(`\n ${AMBER}${BOLD}⬡ tfx multi${RESET} ${alive ? `${GREEN}● active${RESET}` : `${RED}● dead${RESET}`}\n`);
|
|
52
|
-
console.log(` 세션: ${state.sessionName}`);
|
|
53
|
-
console.log(` 모드: ${state.teammateMode || "tmux"}`);
|
|
54
|
-
console.log(` 리드: ${state.lead || "claude"}`);
|
|
55
|
-
console.log(` 워커: ${(state.agents || []).join(", ")}`);
|
|
56
|
-
console.log(` Uptime: ${alive ? `${Math.round((Date.now() - state.startedAt) / 60000)}분` : "-"}`);
|
|
57
|
-
console.log(` 태스크: ${(state.tasks || []).length}`);
|
|
58
|
-
if (isWtMode(state) && !hasWindowsTerminalSession()) {
|
|
59
|
-
console.log(` ${DIM}WT_SESSION 미감지: 생존성은 heuristics로 판정됨${RESET}`);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
for (const member of state.members || []) {
|
|
63
|
-
console.log(` - ${member.name} (${member.cli}) ${DIM}${member.role}${RESET} ${DIM}${member.pane}${RESET}`);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (isNativeMode(state) && alive) {
|
|
67
|
-
for (const member of
|
|
68
|
-
console.log(` • ${member.name}: ${member.status}${formatCompletionSuffix(member)}${member.lastPreview ? ` ${DIM}${member.lastPreview}${RESET}` : ""}`);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (alive) {
|
|
73
|
-
const hubTasks = payload.hubTasks || await fetchHubTaskList(state);
|
|
74
|
-
if (hubTasks.length) {
|
|
75
|
-
const completed = hubTasks.filter((task) => task.status === "completed").length;
|
|
76
|
-
const failed = hubTasks.filter((task) => task.status === "failed").length;
|
|
77
|
-
console.log(`\n ${BOLD}Hub Tasks${RESET} ${DIM}(${completed}/${hubTasks.length} done)${RESET}`);
|
|
78
|
-
for (const task of hubTasks) {
|
|
79
|
-
const icon = task.status === "completed" ? `${GREEN}✓${RESET}` : task.status === "in_progress" ? `${AMBER}●${RESET}` : task.status === "failed" ? `${RED}✗${RESET}` : `${GRAY}○${RESET}`;
|
|
80
|
-
const owner = task.owner ? ` ${GRAY}[${task.owner}]${RESET}` : "";
|
|
81
|
-
console.log(` ${icon} ${task.subject || task.description?.slice(0, 50) || ""}${owner}`);
|
|
82
|
-
}
|
|
83
|
-
if (failed > 0) console.log(` ${RED}⚠ ${failed}건 실패${RESET}`);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
console.log("");
|
|
87
|
-
}
|
|
1
|
+
import { AMBER, BOLD, DIM, GRAY, GREEN, RED, RESET } from "../../shared.mjs";
|
|
2
|
+
import { hasWindowsTerminalSession } from "../../session.mjs";
|
|
3
|
+
import { fetchHubTaskList, nativeGetStatus } from "../services/hub-client.mjs";
|
|
4
|
+
import { isNativeMode, isTeamAlive, isWtMode } from "../services/runtime-mode.mjs";
|
|
5
|
+
import { loadTeamState } from "../services/state-store.mjs";
|
|
6
|
+
import { formatCompletionSuffix } from "../render.mjs";
|
|
7
|
+
|
|
8
|
+
export async function teamStatus(args = []) {
|
|
9
|
+
const json = process.env.TFX_OUTPUT_JSON === "1" || args.includes("--json");
|
|
10
|
+
const state = loadTeamState();
|
|
11
|
+
if (!state) {
|
|
12
|
+
if (json) {
|
|
13
|
+
process.stdout.write(`${JSON.stringify({ status: "offline", sessionName: null, alive: false }, null, 2)}\n`);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
console.log(`\n ${DIM}활성 팀 세션 없음${RESET}\n`);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const alive = isTeamAlive(state);
|
|
21
|
+
const payload = {
|
|
22
|
+
status: alive ? "active" : "dead",
|
|
23
|
+
alive,
|
|
24
|
+
sessionName: state.sessionName,
|
|
25
|
+
teammateMode: state.teammateMode || "tmux",
|
|
26
|
+
lead: state.lead || "claude",
|
|
27
|
+
agents: state.agents || [],
|
|
28
|
+
startedAt: state.startedAt || null,
|
|
29
|
+
taskCount: (state.tasks || []).length,
|
|
30
|
+
members: (state.members || []).map((member) => ({
|
|
31
|
+
name: member.name,
|
|
32
|
+
cli: member.cli,
|
|
33
|
+
role: member.role,
|
|
34
|
+
pane: member.pane,
|
|
35
|
+
})),
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
if (isNativeMode(state) && alive) {
|
|
39
|
+
payload.nativeMembers = (await nativeGetStatus(state))?.data?.members || [];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (alive) {
|
|
43
|
+
payload.hubTasks = await fetchHubTaskList(state);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (json) {
|
|
47
|
+
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
console.log(`\n ${AMBER}${BOLD}⬡ tfx multi${RESET} ${alive ? `${GREEN}● active${RESET}` : `${RED}● dead${RESET}`}\n`);
|
|
52
|
+
console.log(` 세션: ${state.sessionName}`);
|
|
53
|
+
console.log(` 모드: ${state.teammateMode || "tmux"}`);
|
|
54
|
+
console.log(` 리드: ${state.lead || "claude"}`);
|
|
55
|
+
console.log(` 워커: ${(state.agents || []).join(", ")}`);
|
|
56
|
+
console.log(` Uptime: ${alive ? `${Math.round((Date.now() - state.startedAt) / 60000)}분` : "-"}`);
|
|
57
|
+
console.log(` 태스크: ${(state.tasks || []).length}`);
|
|
58
|
+
if (isWtMode(state) && !hasWindowsTerminalSession()) {
|
|
59
|
+
console.log(` ${DIM}WT_SESSION 미감지: 생존성은 heuristics로 판정됨${RESET}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
for (const member of state.members || []) {
|
|
63
|
+
console.log(` - ${member.name} (${member.cli}) ${DIM}${member.role}${RESET} ${DIM}${member.pane}${RESET}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (isNativeMode(state) && alive) {
|
|
67
|
+
for (const member of payload.nativeMembers) {
|
|
68
|
+
console.log(` • ${member.name}: ${member.status}${formatCompletionSuffix(member)}${member.lastPreview ? ` ${DIM}${member.lastPreview}${RESET}` : ""}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (alive) {
|
|
73
|
+
const hubTasks = payload.hubTasks || await fetchHubTaskList(state);
|
|
74
|
+
if (hubTasks.length) {
|
|
75
|
+
const completed = hubTasks.filter((task) => task.status === "completed").length;
|
|
76
|
+
const failed = hubTasks.filter((task) => task.status === "failed").length;
|
|
77
|
+
console.log(`\n ${BOLD}Hub Tasks${RESET} ${DIM}(${completed}/${hubTasks.length} done)${RESET}`);
|
|
78
|
+
for (const task of hubTasks) {
|
|
79
|
+
const icon = task.status === "completed" ? `${GREEN}✓${RESET}` : task.status === "in_progress" ? `${AMBER}●${RESET}` : task.status === "failed" ? `${RED}✗${RESET}` : `${GRAY}○${RESET}`;
|
|
80
|
+
const owner = task.owner ? ` ${GRAY}[${task.owner}]${RESET}` : "";
|
|
81
|
+
console.log(` ${icon} ${task.subject || task.description?.slice(0, 50) || ""}${owner}`);
|
|
82
|
+
}
|
|
83
|
+
if (failed > 0) console.log(` ${RED}⚠ ${failed}건 실패${RESET}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
console.log("");
|
|
87
|
+
}
|
|
@@ -24,7 +24,7 @@ export function teamTaskUpdate(args = []) {
|
|
|
24
24
|
return;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
saveTeamState({ ...state, tasks: updated.tasks });
|
|
27
|
+
saveTeamState({ ...state, tasks: updated.tasks }, state.sessionId);
|
|
28
28
|
ok(`${updated.target.id} 상태 갱신: ${nextStatus}`);
|
|
29
29
|
console.log("");
|
|
30
30
|
}
|
package/hub/team/cli/index.mjs
CHANGED
|
@@ -1,39 +1,41 @@
|
|
|
1
|
-
import { renderTeamHelp } from "./help.mjs";
|
|
2
|
-
import { resolveTeamCommand } from "./manifest.mjs";
|
|
3
|
-
import { teamAttach } from "./commands/attach.mjs";
|
|
4
|
-
import { teamControl } from "./commands/control.mjs";
|
|
5
|
-
import { teamDebug } from "./commands/debug.mjs";
|
|
6
|
-
import { teamFocus } from "./commands/focus.mjs";
|
|
7
|
-
import { teamInterrupt } from "./commands/interrupt.mjs";
|
|
8
|
-
import { teamKill } from "./commands/kill.mjs";
|
|
9
|
-
import { teamList } from "./commands/list.mjs";
|
|
10
|
-
import { teamSend } from "./commands/send.mjs";
|
|
11
|
-
import { teamStart } from "./commands/start/index.mjs";
|
|
12
|
-
import { teamStatus } from "./commands/status.mjs";
|
|
13
|
-
import { teamStop } from "./commands/stop.mjs";
|
|
14
|
-
import { teamTaskUpdate } from "./commands/task.mjs";
|
|
15
|
-
import { teamTasks } from "./commands/tasks.mjs";
|
|
16
|
-
|
|
17
|
-
const handlers = {
|
|
18
|
-
attach: teamAttach,
|
|
19
|
-
control: teamControl,
|
|
20
|
-
debug: teamDebug,
|
|
21
|
-
focus: teamFocus,
|
|
22
|
-
help: renderTeamHelp,
|
|
23
|
-
interrupt: teamInterrupt,
|
|
24
|
-
kill: teamKill,
|
|
25
|
-
list: teamList,
|
|
26
|
-
send: teamSend,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
if (!
|
|
38
|
-
|
|
39
|
-
|
|
1
|
+
import { renderTeamHelp } from "./help.mjs";
|
|
2
|
+
import { resolveTeamCommand } from "./manifest.mjs";
|
|
3
|
+
import { teamAttach } from "./commands/attach.mjs";
|
|
4
|
+
import { teamControl } from "./commands/control.mjs";
|
|
5
|
+
import { teamDebug } from "./commands/debug.mjs";
|
|
6
|
+
import { teamFocus } from "./commands/focus.mjs";
|
|
7
|
+
import { teamInterrupt } from "./commands/interrupt.mjs";
|
|
8
|
+
import { teamKill } from "./commands/kill.mjs";
|
|
9
|
+
import { teamList } from "./commands/list.mjs";
|
|
10
|
+
import { teamSend } from "./commands/send.mjs";
|
|
11
|
+
import { teamStart } from "./commands/start/index.mjs";
|
|
12
|
+
import { teamStatus } from "./commands/status.mjs";
|
|
13
|
+
import { teamStop } from "./commands/stop.mjs";
|
|
14
|
+
import { teamTaskUpdate } from "./commands/task.mjs";
|
|
15
|
+
import { teamTasks } from "./commands/tasks.mjs";
|
|
16
|
+
|
|
17
|
+
const handlers = {
|
|
18
|
+
attach: teamAttach,
|
|
19
|
+
control: teamControl,
|
|
20
|
+
debug: teamDebug,
|
|
21
|
+
focus: teamFocus,
|
|
22
|
+
help: renderTeamHelp,
|
|
23
|
+
interrupt: teamInterrupt,
|
|
24
|
+
kill: teamKill,
|
|
25
|
+
list: teamList,
|
|
26
|
+
send: teamSend,
|
|
27
|
+
start: teamStart,
|
|
28
|
+
status: teamStatus,
|
|
29
|
+
stop: teamStop,
|
|
30
|
+
task: teamTaskUpdate,
|
|
31
|
+
tasks: teamTasks,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export async function cmdTeam() {
|
|
35
|
+
const args = process.argv.slice(3);
|
|
36
|
+
const command = resolveTeamCommand(args[0]);
|
|
37
|
+
if (!args.length) return renderTeamHelp();
|
|
38
|
+
// 미등록 커맨드는 teamStart로 fallthrough (팀 생성 기본값)
|
|
39
|
+
if (!command) return teamStart(args);
|
|
40
|
+
return handlers[command](args.slice(1));
|
|
41
|
+
}
|