triflux 10.3.2 → 10.3.4
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/plugin.json +22 -22
- package/LICENSE +21 -21
- package/README.ko.md +16 -0
- package/README.md +8 -0
- package/hooks/hook-registry.json +256 -256
- package/hub/adaptive-inject.mjs +1 -1
- package/hub/assign-callbacks.mjs +120 -120
- package/hub/delegator/index.mjs +14 -14
- package/hub/delegator/tool-definitions.mjs +35 -35
- package/hub/hitl.mjs +143 -143
- package/hub/lib/path-utils.mjs +167 -0
- package/hub/router.mjs +791 -791
- package/hub/session-fingerprint.mjs +1 -1
- package/hub/team/cli/commands/attach.mjs +37 -37
- package/hub/team/cli/commands/debug.mjs +74 -74
- package/hub/team/cli/commands/focus.mjs +53 -53
- package/hub/team/cli/commands/list.mjs +24 -24
- package/hub/team/cli/commands/start/start-in-process.mjs +40 -40
- package/hub/team/cli/commands/start/start-mux.mjs +73 -73
- package/hub/team/cli/commands/start/start-wt.mjs +69 -69
- package/hub/team/cli/commands/tasks.mjs +13 -13
- package/hub/team/cli/render.mjs +30 -30
- package/hub/team/cli/services/attach-fallback.mjs +54 -54
- package/hub/team/cli/services/member-selector.mjs +30 -30
- package/hub/team/cli/services/native-control.mjs +116 -116
- package/hub/team/cli/services/task-model.mjs +30 -30
- package/hub/team/notify.mjs +1 -1
- package/hub/team/orchestrator.mjs +161 -161
- package/hub/team/runtime-strategy.mjs +74 -0
- package/hub/team/session.mjs +611 -611
- package/hub/team/shared.mjs +13 -13
- package/hub/team/worktree-lifecycle.mjs +61 -2
- package/hub/tray.mjs +368 -368
- package/hub/workers/codex-mcp.mjs +507 -507
- package/hub/workers/factory.mjs +21 -21
- package/hud/hud-qos-status.mjs +17 -3
- package/hud/mission-board.mjs +53 -0
- package/hud/providers/claude.mjs +95 -22
- package/hud/renderers.mjs +39 -5
- package/mesh/index.mjs +63 -0
- package/mesh/mesh-budget.mjs +128 -0
- package/mesh/mesh-heartbeat.mjs +100 -0
- package/mesh/mesh-protocol.mjs +96 -0
- package/mesh/mesh-queue.mjs +165 -0
- package/mesh/mesh-registry.mjs +78 -0
- package/mesh/mesh-router.mjs +76 -0
- package/package.json +2 -1
- package/scripts/completions/tfx.bash +47 -47
- package/scripts/completions/tfx.fish +44 -44
- package/scripts/completions/tfx.zsh +83 -83
- package/scripts/demo.mjs +169 -0
- package/scripts/headless-guard.mjs +16 -4
- package/scripts/hub-ensure.mjs +120 -120
- package/scripts/keyword-detector.mjs +272 -272
- package/scripts/keyword-rules-expander.mjs +521 -521
- package/scripts/lib/mcp-server-catalog.mjs +118 -118
- package/scripts/lib/skill-state.mjs +220 -0
- package/scripts/notion-read.mjs +553 -553
- package/scripts/test-tfx-route-no-claude-native.mjs +57 -57
- package/scripts/tfx-batch-stats.mjs +96 -96
- package/skills/.omc/state/agent-replay-8f0e10a9-9693-4410-96f5-a6b07e8ed995.jsonl +0 -1
- package/skills/.omc/state/idle-notif-cooldown.json +0 -3
- package/skills/.omc/state/last-tool-error.json +0 -7
- package/skills/.omc/state/subagent-tracking.json +0 -7
- package/skills/tfx-remote-spawn/references/hosts.json +0 -16
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
import { attachSession } from "../../session.mjs";
|
|
2
|
-
import { DIM, RESET } from "../../shared.mjs";
|
|
3
|
-
import { buildManualAttachCommand, launchAttachInWindowsTerminal, wantsWtAttachFallback } from "../services/attach-fallback.mjs";
|
|
4
|
-
import { isNativeMode, isTeamAlive, isWtMode } from "../services/runtime-mode.mjs";
|
|
5
|
-
import { loadTeamState } from "../services/state-store.mjs";
|
|
6
|
-
import { fail, ok, warn } from "../render.mjs";
|
|
7
|
-
|
|
8
|
-
export async function teamAttach(args = []) {
|
|
9
|
-
const state = loadTeamState();
|
|
10
|
-
if (!state || !isTeamAlive(state)) {
|
|
11
|
-
console.log(`\n ${DIM}활성 팀 세션 없음${RESET}\n`);
|
|
12
|
-
return;
|
|
13
|
-
}
|
|
14
|
-
if (isNativeMode(state)) {
|
|
15
|
-
console.log(`\n ${DIM}in-process 모드는 별도 attach가 없습니다.${RESET}\n ${DIM}상태 확인: tfx multi status${RESET}\n`);
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
if (isWtMode(state)) {
|
|
19
|
-
console.log(`\n ${DIM}wt 모드는 attach 개념이 없습니다 (Windows Terminal pane가 독립 실행됨).${RESET}\n ${DIM}재실행/정리는: tfx multi stop${RESET}\n`);
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
try {
|
|
24
|
-
attachSession(state.sessionName);
|
|
25
|
-
} catch (error) {
|
|
26
|
-
const allowWt = wantsWtAttachFallback(args);
|
|
27
|
-
if (allowWt && await launchAttachInWindowsTerminal(state.sessionName)) {
|
|
28
|
-
warn(`현재 터미널에서 attach 실패: ${error.message}`);
|
|
29
|
-
ok("Windows Terminal split-pane로 attach 재시도 창을 열었습니다.");
|
|
30
|
-
console.log(` ${DIM}수동 attach 명령: ${buildManualAttachCommand(state.sessionName)}${RESET}\n`);
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
fail(`attach 실패: ${error.message}`);
|
|
34
|
-
warn(allowWt ? "WT 분할창 attach 자동 검증 실패 (session_attached 증가 없음)" : "자동 WT 분할은 기본 비활성입니다. 필요 시 --wt 옵션으로 실행하세요.");
|
|
35
|
-
console.log(` ${DIM}수동 attach 명령: ${buildManualAttachCommand(state.sessionName)}${RESET}\n`);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
1
|
+
import { attachSession } from "../../session.mjs";
|
|
2
|
+
import { DIM, RESET } from "../../shared.mjs";
|
|
3
|
+
import { buildManualAttachCommand, launchAttachInWindowsTerminal, wantsWtAttachFallback } from "../services/attach-fallback.mjs";
|
|
4
|
+
import { isNativeMode, isTeamAlive, isWtMode } from "../services/runtime-mode.mjs";
|
|
5
|
+
import { loadTeamState } from "../services/state-store.mjs";
|
|
6
|
+
import { fail, ok, warn } from "../render.mjs";
|
|
7
|
+
|
|
8
|
+
export async function teamAttach(args = []) {
|
|
9
|
+
const state = loadTeamState();
|
|
10
|
+
if (!state || !isTeamAlive(state)) {
|
|
11
|
+
console.log(`\n ${DIM}활성 팀 세션 없음${RESET}\n`);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (isNativeMode(state)) {
|
|
15
|
+
console.log(`\n ${DIM}in-process 모드는 별도 attach가 없습니다.${RESET}\n ${DIM}상태 확인: tfx multi status${RESET}\n`);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (isWtMode(state)) {
|
|
19
|
+
console.log(`\n ${DIM}wt 모드는 attach 개념이 없습니다 (Windows Terminal pane가 독립 실행됨).${RESET}\n ${DIM}재실행/정리는: tfx multi stop${RESET}\n`);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
attachSession(state.sessionName);
|
|
25
|
+
} catch (error) {
|
|
26
|
+
const allowWt = wantsWtAttachFallback(args);
|
|
27
|
+
if (allowWt && await launchAttachInWindowsTerminal(state.sessionName)) {
|
|
28
|
+
warn(`현재 터미널에서 attach 실패: ${error.message}`);
|
|
29
|
+
ok("Windows Terminal split-pane로 attach 재시도 창을 열었습니다.");
|
|
30
|
+
console.log(` ${DIM}수동 attach 명령: ${buildManualAttachCommand(state.sessionName)}${RESET}\n`);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
fail(`attach 실패: ${error.message}`);
|
|
34
|
+
warn(allowWt ? "WT 분할창 attach 자동 검증 실패 (session_attached 증가 없음)" : "자동 WT 분할은 기본 비활성입니다. 필요 시 --wt 옵션으로 실행하세요.");
|
|
35
|
+
console.log(` ${DIM}수동 attach 명령: ${buildManualAttachCommand(state.sessionName)}${RESET}\n`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -1,74 +1,74 @@
|
|
|
1
|
-
import { AMBER, BOLD, DIM, RESET } from "../../shared.mjs";
|
|
2
|
-
import {
|
|
3
|
-
capturePaneOutput,
|
|
4
|
-
detectMultiplexer,
|
|
5
|
-
getSessionAttachedCount,
|
|
6
|
-
hasWindowsTerminal,
|
|
7
|
-
hasWindowsTerminalSession,
|
|
8
|
-
listSessions,
|
|
9
|
-
} from "../../session.mjs";
|
|
10
|
-
import { getHubInfo, nativeGetStatus } from "../services/hub-client.mjs";
|
|
11
|
-
import { isNativeMode, isTeamAlive, isWtMode } from "../services/runtime-mode.mjs";
|
|
12
|
-
import { loadTeamState, TEAM_PROFILE } from "../services/state-store.mjs";
|
|
13
|
-
import { formatCompletionSuffix } from "../render.mjs";
|
|
14
|
-
|
|
15
|
-
export async function teamDebug(args = []) {
|
|
16
|
-
const state = loadTeamState();
|
|
17
|
-
const flagIndex = args.findIndex((arg) => arg === "--lines" || arg === "-n");
|
|
18
|
-
const lines = flagIndex === -1 ? 20 : Math.max(3, parseInt(args[flagIndex + 1] || "20", 10) || 20);
|
|
19
|
-
const hub = await getHubInfo();
|
|
20
|
-
|
|
21
|
-
console.log(`\n ${AMBER}${BOLD}⬡ Team Debug${RESET}\n`);
|
|
22
|
-
console.log(` platform: ${process.platform}`);
|
|
23
|
-
console.log(` node: ${process.version}`);
|
|
24
|
-
console.log(` tty: stdout=${!!process.stdout.isTTY}, stdin=${!!process.stdin.isTTY}`);
|
|
25
|
-
console.log(` mux: ${detectMultiplexer() || "none"}`);
|
|
26
|
-
console.log(` hub-pid: ${hub ? `${hub.pid}` : "-"}`);
|
|
27
|
-
console.log(` hub-url: ${hub?.url || "-"}`);
|
|
28
|
-
const sessions = listSessions();
|
|
29
|
-
console.log(` sessions: ${sessions.length ? sessions.join(", ") : "-"}`);
|
|
30
|
-
|
|
31
|
-
if (!state) {
|
|
32
|
-
console.log(`\n ${DIM}team-state 없음 (활성 세션 없음)${RESET}\n`);
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
console.log(`\n ${BOLD}state${RESET}`);
|
|
37
|
-
console.log(` session: ${state.sessionName}`);
|
|
38
|
-
console.log(` profile: ${state.profile || TEAM_PROFILE}`);
|
|
39
|
-
console.log(` mode: ${state.teammateMode || "tmux"}`);
|
|
40
|
-
console.log(` lead: ${state.lead}`);
|
|
41
|
-
console.log(` agents: ${(state.agents || []).join(", ")}`);
|
|
42
|
-
console.log(` alive: ${isTeamAlive(state) ? "yes" : "no"}`);
|
|
43
|
-
console.log(` attached: ${getSessionAttachedCount(state.sessionName) ?? "-"}`);
|
|
44
|
-
|
|
45
|
-
if (isWtMode(state)) {
|
|
46
|
-
console.log(`\n ${BOLD}wt-session${RESET}`);
|
|
47
|
-
console.log(` window: ${state?.wt?.windowId ?? 0}`);
|
|
48
|
-
console.log(` layout: ${state?.wt?.layout || state?.layout || "-"}`);
|
|
49
|
-
console.log(` panes: ${state?.wt?.paneCount ?? (state.members || []).length}`);
|
|
50
|
-
console.log(` wt.exe: ${hasWindowsTerminal() ? "yes" : "no"}`);
|
|
51
|
-
console.log(` WT_SESSION:${hasWindowsTerminalSession() ? "yes" : "no"}`);
|
|
52
|
-
console.log("");
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (isNativeMode(state)) {
|
|
57
|
-
console.log(`\n ${BOLD}native-members${RESET}`);
|
|
58
|
-
const members = (await nativeGetStatus(state))?.data?.members || [];
|
|
59
|
-
if (!members.length) console.log(` ${DIM}(no data)${RESET}`);
|
|
60
|
-
for (const member of members) console.log(` - ${member.name}: ${member.status}${formatCompletionSuffix(member)}${member.lastPreview ? ` ${DIM}${member.lastPreview}${RESET}` : ""}`);
|
|
61
|
-
console.log("");
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
console.log(`\n ${BOLD}pane-tail${RESET} ${DIM}(last ${lines} lines)${RESET}`);
|
|
66
|
-
if (!(state.members || []).length) console.log(` ${DIM}(members 없음)${RESET}`);
|
|
67
|
-
for (const member of state.members || []) {
|
|
68
|
-
console.log(`\n [${member.name}] ${member.pane}`);
|
|
69
|
-
for (const line of (capturePaneOutput(member.pane, lines) || "(empty)").split("\n").slice(-lines)) {
|
|
70
|
-
console.log(` ${line}`);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
console.log("");
|
|
74
|
-
}
|
|
1
|
+
import { AMBER, BOLD, DIM, RESET } from "../../shared.mjs";
|
|
2
|
+
import {
|
|
3
|
+
capturePaneOutput,
|
|
4
|
+
detectMultiplexer,
|
|
5
|
+
getSessionAttachedCount,
|
|
6
|
+
hasWindowsTerminal,
|
|
7
|
+
hasWindowsTerminalSession,
|
|
8
|
+
listSessions,
|
|
9
|
+
} from "../../session.mjs";
|
|
10
|
+
import { getHubInfo, nativeGetStatus } from "../services/hub-client.mjs";
|
|
11
|
+
import { isNativeMode, isTeamAlive, isWtMode } from "../services/runtime-mode.mjs";
|
|
12
|
+
import { loadTeamState, TEAM_PROFILE } from "../services/state-store.mjs";
|
|
13
|
+
import { formatCompletionSuffix } from "../render.mjs";
|
|
14
|
+
|
|
15
|
+
export async function teamDebug(args = []) {
|
|
16
|
+
const state = loadTeamState();
|
|
17
|
+
const flagIndex = args.findIndex((arg) => arg === "--lines" || arg === "-n");
|
|
18
|
+
const lines = flagIndex === -1 ? 20 : Math.max(3, parseInt(args[flagIndex + 1] || "20", 10) || 20);
|
|
19
|
+
const hub = await getHubInfo();
|
|
20
|
+
|
|
21
|
+
console.log(`\n ${AMBER}${BOLD}⬡ Team Debug${RESET}\n`);
|
|
22
|
+
console.log(` platform: ${process.platform}`);
|
|
23
|
+
console.log(` node: ${process.version}`);
|
|
24
|
+
console.log(` tty: stdout=${!!process.stdout.isTTY}, stdin=${!!process.stdin.isTTY}`);
|
|
25
|
+
console.log(` mux: ${detectMultiplexer() || "none"}`);
|
|
26
|
+
console.log(` hub-pid: ${hub ? `${hub.pid}` : "-"}`);
|
|
27
|
+
console.log(` hub-url: ${hub?.url || "-"}`);
|
|
28
|
+
const sessions = listSessions();
|
|
29
|
+
console.log(` sessions: ${sessions.length ? sessions.join(", ") : "-"}`);
|
|
30
|
+
|
|
31
|
+
if (!state) {
|
|
32
|
+
console.log(`\n ${DIM}team-state 없음 (활성 세션 없음)${RESET}\n`);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
console.log(`\n ${BOLD}state${RESET}`);
|
|
37
|
+
console.log(` session: ${state.sessionName}`);
|
|
38
|
+
console.log(` profile: ${state.profile || TEAM_PROFILE}`);
|
|
39
|
+
console.log(` mode: ${state.teammateMode || "tmux"}`);
|
|
40
|
+
console.log(` lead: ${state.lead}`);
|
|
41
|
+
console.log(` agents: ${(state.agents || []).join(", ")}`);
|
|
42
|
+
console.log(` alive: ${isTeamAlive(state) ? "yes" : "no"}`);
|
|
43
|
+
console.log(` attached: ${getSessionAttachedCount(state.sessionName) ?? "-"}`);
|
|
44
|
+
|
|
45
|
+
if (isWtMode(state)) {
|
|
46
|
+
console.log(`\n ${BOLD}wt-session${RESET}`);
|
|
47
|
+
console.log(` window: ${state?.wt?.windowId ?? 0}`);
|
|
48
|
+
console.log(` layout: ${state?.wt?.layout || state?.layout || "-"}`);
|
|
49
|
+
console.log(` panes: ${state?.wt?.paneCount ?? (state.members || []).length}`);
|
|
50
|
+
console.log(` wt.exe: ${hasWindowsTerminal() ? "yes" : "no"}`);
|
|
51
|
+
console.log(` WT_SESSION:${hasWindowsTerminalSession() ? "yes" : "no"}`);
|
|
52
|
+
console.log("");
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (isNativeMode(state)) {
|
|
57
|
+
console.log(`\n ${BOLD}native-members${RESET}`);
|
|
58
|
+
const members = (await nativeGetStatus(state))?.data?.members || [];
|
|
59
|
+
if (!members.length) console.log(` ${DIM}(no data)${RESET}`);
|
|
60
|
+
for (const member of members) console.log(` - ${member.name}: ${member.status}${formatCompletionSuffix(member)}${member.lastPreview ? ` ${DIM}${member.lastPreview}${RESET}` : ""}`);
|
|
61
|
+
console.log("");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
console.log(`\n ${BOLD}pane-tail${RESET} ${DIM}(last ${lines} lines)${RESET}`);
|
|
66
|
+
if (!(state.members || []).length) console.log(` ${DIM}(members 없음)${RESET}`);
|
|
67
|
+
for (const member of state.members || []) {
|
|
68
|
+
console.log(`\n [${member.name}] ${member.pane}`);
|
|
69
|
+
for (const line of (capturePaneOutput(member.pane, lines) || "(empty)").split("\n").slice(-lines)) {
|
|
70
|
+
console.log(` ${line}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
console.log("");
|
|
74
|
+
}
|
|
@@ -1,53 +1,53 @@
|
|
|
1
|
-
import { attachSession, focusPane, focusWtPane } from "../../session.mjs";
|
|
2
|
-
import { DIM, RESET, WHITE } from "../../shared.mjs";
|
|
3
|
-
import { buildManualAttachCommand, launchAttachInWindowsTerminal, wantsWtAttachFallback } from "../services/attach-fallback.mjs";
|
|
4
|
-
import { resolveMember } from "../services/member-selector.mjs";
|
|
5
|
-
import { isNativeMode, isTeamAlive, isWtMode } from "../services/runtime-mode.mjs";
|
|
6
|
-
import { loadTeamState } from "../services/state-store.mjs";
|
|
7
|
-
import { fail, ok, warn } from "../render.mjs";
|
|
8
|
-
|
|
9
|
-
export async function teamFocus(args = []) {
|
|
10
|
-
const state = loadTeamState();
|
|
11
|
-
if (!state || !isTeamAlive(state)) {
|
|
12
|
-
console.log(`\n ${DIM}활성 팀 세션 없음${RESET}\n`);
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
if (isNativeMode(state)) {
|
|
16
|
-
console.log(`\n ${DIM}in-process 모드는 focus/attach 개념이 없습니다.${RESET}\n ${DIM}직접 지시: tfx multi send <대상> "메시지"${RESET}\n`);
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const member = resolveMember(state, args[0]);
|
|
21
|
-
if (!member) {
|
|
22
|
-
console.log(`\n 사용법: ${WHITE}tfx multi focus <lead|이름|번호>${RESET}\n`);
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (isWtMode(state)) {
|
|
27
|
-
const paneIndex = Number(/^wt:(\d+)$/.exec(member.pane || "")?.[1]);
|
|
28
|
-
if (!Number.isFinite(paneIndex)) {
|
|
29
|
-
console.log(`\n ${DIM}wt pane 인덱스 파싱 실패: ${member.pane}${RESET}\n`);
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
if (focusWtPane(paneIndex, { layout: state?.wt?.layout || state?.layout || "1xN" })) ok(`${member.name} pane 포커스 이동 (wt)`);
|
|
33
|
-
else warn("wt pane 포커스 이동 실패 (WT_SESSION/wt.exe 상태 확인 필요)");
|
|
34
|
-
console.log("");
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
focusPane(member.pane, { zoom: false });
|
|
39
|
-
try {
|
|
40
|
-
attachSession(state.sessionName);
|
|
41
|
-
} catch (error) {
|
|
42
|
-
const allowWt = wantsWtAttachFallback(args);
|
|
43
|
-
if (allowWt && await launchAttachInWindowsTerminal(state.sessionName)) {
|
|
44
|
-
warn(`현재 터미널에서 attach 실패: ${error.message}`);
|
|
45
|
-
ok("Windows Terminal split-pane로 attach 재시도 창을 열었습니다.");
|
|
46
|
-
console.log(` ${DIM}수동 attach 명령: ${buildManualAttachCommand(state.sessionName)}${RESET}\n`);
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
fail(`attach 실패: ${error.message}`);
|
|
50
|
-
warn(allowWt ? "WT 분할창 attach 자동 검증 실패 (session_attached 증가 없음)" : "자동 WT 분할은 기본 비활성입니다. 필요 시 --wt 옵션으로 실행하세요.");
|
|
51
|
-
console.log(` ${DIM}수동 attach 명령: ${buildManualAttachCommand(state.sessionName)}${RESET}\n`);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
1
|
+
import { attachSession, focusPane, focusWtPane } from "../../session.mjs";
|
|
2
|
+
import { DIM, RESET, WHITE } from "../../shared.mjs";
|
|
3
|
+
import { buildManualAttachCommand, launchAttachInWindowsTerminal, wantsWtAttachFallback } from "../services/attach-fallback.mjs";
|
|
4
|
+
import { resolveMember } from "../services/member-selector.mjs";
|
|
5
|
+
import { isNativeMode, isTeamAlive, isWtMode } from "../services/runtime-mode.mjs";
|
|
6
|
+
import { loadTeamState } from "../services/state-store.mjs";
|
|
7
|
+
import { fail, ok, warn } from "../render.mjs";
|
|
8
|
+
|
|
9
|
+
export async function teamFocus(args = []) {
|
|
10
|
+
const state = loadTeamState();
|
|
11
|
+
if (!state || !isTeamAlive(state)) {
|
|
12
|
+
console.log(`\n ${DIM}활성 팀 세션 없음${RESET}\n`);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
if (isNativeMode(state)) {
|
|
16
|
+
console.log(`\n ${DIM}in-process 모드는 focus/attach 개념이 없습니다.${RESET}\n ${DIM}직접 지시: tfx multi send <대상> "메시지"${RESET}\n`);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const member = resolveMember(state, args[0]);
|
|
21
|
+
if (!member) {
|
|
22
|
+
console.log(`\n 사용법: ${WHITE}tfx multi focus <lead|이름|번호>${RESET}\n`);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (isWtMode(state)) {
|
|
27
|
+
const paneIndex = Number(/^wt:(\d+)$/.exec(member.pane || "")?.[1]);
|
|
28
|
+
if (!Number.isFinite(paneIndex)) {
|
|
29
|
+
console.log(`\n ${DIM}wt pane 인덱스 파싱 실패: ${member.pane}${RESET}\n`);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (focusWtPane(paneIndex, { layout: state?.wt?.layout || state?.layout || "1xN" })) ok(`${member.name} pane 포커스 이동 (wt)`);
|
|
33
|
+
else warn("wt pane 포커스 이동 실패 (WT_SESSION/wt.exe 상태 확인 필요)");
|
|
34
|
+
console.log("");
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
focusPane(member.pane, { zoom: false });
|
|
39
|
+
try {
|
|
40
|
+
attachSession(state.sessionName);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
const allowWt = wantsWtAttachFallback(args);
|
|
43
|
+
if (allowWt && await launchAttachInWindowsTerminal(state.sessionName)) {
|
|
44
|
+
warn(`현재 터미널에서 attach 실패: ${error.message}`);
|
|
45
|
+
ok("Windows Terminal split-pane로 attach 재시도 창을 열었습니다.");
|
|
46
|
+
console.log(` ${DIM}수동 attach 명령: ${buildManualAttachCommand(state.sessionName)}${RESET}\n`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
fail(`attach 실패: ${error.message}`);
|
|
50
|
+
warn(allowWt ? "WT 분할창 attach 자동 검증 실패 (session_attached 증가 없음)" : "자동 WT 분할은 기본 비활성입니다. 필요 시 --wt 옵션으로 실행하세요.");
|
|
51
|
+
console.log(` ${DIM}수동 attach 명령: ${buildManualAttachCommand(state.sessionName)}${RESET}\n`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
import { AMBER, BOLD, DIM, GREEN, RESET } from "../../shared.mjs";
|
|
2
|
-
import { listSessions } from "../../session.mjs";
|
|
3
|
-
import { isNativeMode, isTeamAlive, isWtMode } from "../services/runtime-mode.mjs";
|
|
4
|
-
import { loadTeamState } from "../services/state-store.mjs";
|
|
5
|
-
|
|
6
|
-
export function teamList() {
|
|
7
|
-
const state = loadTeamState();
|
|
8
|
-
if (state && isTeamAlive(state) && (isNativeMode(state) || isWtMode(state))) {
|
|
9
|
-
console.log(`\n ${AMBER}${BOLD}⬡ 팀 세션 목록${RESET}\n`);
|
|
10
|
-
console.log(` ${GREEN}●${RESET} ${state.sessionName} ${DIM}(${isNativeMode(state) ? "in-process" : "wt"})${RESET}`);
|
|
11
|
-
console.log("");
|
|
12
|
-
return;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const sessions = listSessions();
|
|
16
|
-
if (!sessions.length) {
|
|
17
|
-
console.log(`\n ${DIM}활성 팀 세션 없음${RESET}\n`);
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
console.log(`\n ${AMBER}${BOLD}⬡ 팀 세션 목록${RESET}\n`);
|
|
22
|
-
for (const session of sessions) console.log(` ${GREEN}●${RESET} ${session}`);
|
|
23
|
-
console.log("");
|
|
24
|
-
}
|
|
1
|
+
import { AMBER, BOLD, DIM, GREEN, RESET } from "../../shared.mjs";
|
|
2
|
+
import { listSessions } from "../../session.mjs";
|
|
3
|
+
import { isNativeMode, isTeamAlive, isWtMode } from "../services/runtime-mode.mjs";
|
|
4
|
+
import { loadTeamState } from "../services/state-store.mjs";
|
|
5
|
+
|
|
6
|
+
export function teamList() {
|
|
7
|
+
const state = loadTeamState();
|
|
8
|
+
if (state && isTeamAlive(state) && (isNativeMode(state) || isWtMode(state))) {
|
|
9
|
+
console.log(`\n ${AMBER}${BOLD}⬡ 팀 세션 목록${RESET}\n`);
|
|
10
|
+
console.log(` ${GREEN}●${RESET} ${state.sessionName} ${DIM}(${isNativeMode(state) ? "in-process" : "wt"})${RESET}`);
|
|
11
|
+
console.log("");
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const sessions = listSessions();
|
|
16
|
+
if (!sessions.length) {
|
|
17
|
+
console.log(`\n ${DIM}활성 팀 세션 없음${RESET}\n`);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
console.log(`\n ${AMBER}${BOLD}⬡ 팀 세션 목록${RESET}\n`);
|
|
22
|
+
for (const session of sessions) console.log(` ${GREEN}●${RESET} ${session}`);
|
|
23
|
+
console.log("");
|
|
24
|
+
}
|
|
@@ -1,40 +1,40 @@
|
|
|
1
|
-
import { startNativeSupervisor } from "../../services/native-control.mjs";
|
|
2
|
-
import { buildTasks } from "../../services/task-model.mjs";
|
|
3
|
-
|
|
4
|
-
export async function startInProcessTeam({ sessionId, task, lead, agents, subtasks, hubUrl }) {
|
|
5
|
-
const { runtime, members } = await startNativeSupervisor({
|
|
6
|
-
sessionId,
|
|
7
|
-
task,
|
|
8
|
-
lead,
|
|
9
|
-
agents,
|
|
10
|
-
subtasks,
|
|
11
|
-
hubUrl,
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
if (!runtime?.controlUrl) return null;
|
|
15
|
-
|
|
16
|
-
return {
|
|
17
|
-
sessionName: sessionId,
|
|
18
|
-
task,
|
|
19
|
-
lead,
|
|
20
|
-
agents,
|
|
21
|
-
layout: "native",
|
|
22
|
-
teammateMode: "in-process",
|
|
23
|
-
startedAt: Date.now(),
|
|
24
|
-
hubUrl,
|
|
25
|
-
members: members.map((member, index) => ({
|
|
26
|
-
role: member.role,
|
|
27
|
-
name: member.name,
|
|
28
|
-
cli: member.cli,
|
|
29
|
-
agentId: member.agentId,
|
|
30
|
-
pane: `native:${index}`,
|
|
31
|
-
subtask: member.subtask || null,
|
|
32
|
-
})),
|
|
33
|
-
panes: {},
|
|
34
|
-
tasks: buildTasks(subtasks, members.filter((member) => member.role === "worker")),
|
|
35
|
-
native: {
|
|
36
|
-
controlUrl: runtime.controlUrl,
|
|
37
|
-
supervisorPid: runtime.supervisorPid,
|
|
38
|
-
},
|
|
39
|
-
};
|
|
40
|
-
}
|
|
1
|
+
import { startNativeSupervisor } from "../../services/native-control.mjs";
|
|
2
|
+
import { buildTasks } from "../../services/task-model.mjs";
|
|
3
|
+
|
|
4
|
+
export async function startInProcessTeam({ sessionId, task, lead, agents, subtasks, hubUrl }) {
|
|
5
|
+
const { runtime, members } = await startNativeSupervisor({
|
|
6
|
+
sessionId,
|
|
7
|
+
task,
|
|
8
|
+
lead,
|
|
9
|
+
agents,
|
|
10
|
+
subtasks,
|
|
11
|
+
hubUrl,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
if (!runtime?.controlUrl) return null;
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
sessionName: sessionId,
|
|
18
|
+
task,
|
|
19
|
+
lead,
|
|
20
|
+
agents,
|
|
21
|
+
layout: "native",
|
|
22
|
+
teammateMode: "in-process",
|
|
23
|
+
startedAt: Date.now(),
|
|
24
|
+
hubUrl,
|
|
25
|
+
members: members.map((member, index) => ({
|
|
26
|
+
role: member.role,
|
|
27
|
+
name: member.name,
|
|
28
|
+
cli: member.cli,
|
|
29
|
+
agentId: member.agentId,
|
|
30
|
+
pane: `native:${index}`,
|
|
31
|
+
subtask: member.subtask || null,
|
|
32
|
+
})),
|
|
33
|
+
panes: {},
|
|
34
|
+
tasks: buildTasks(subtasks, members.filter((member) => member.role === "worker")),
|
|
35
|
+
native: {
|
|
36
|
+
controlUrl: runtime.controlUrl,
|
|
37
|
+
supervisorPid: runtime.supervisorPid,
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -1,73 +1,73 @@
|
|
|
1
|
-
import { join } from "node:path";
|
|
2
|
-
|
|
3
|
-
import { buildCliCommand, startCliInPane } from "../../../pane.mjs";
|
|
4
|
-
import { orchestrate } from "../../../orchestrator.mjs";
|
|
5
|
-
import { attachSession, configureTeammateKeybindings, createSession } from "../../../session.mjs";
|
|
6
|
-
import { BOLD, DIM, GREEN, RESET } from "../../../shared.mjs";
|
|
7
|
-
import { toAgentId } from "../../services/member-selector.mjs";
|
|
8
|
-
import { PKG_ROOT, TEAM_PROFILE } from "../../services/state-store.mjs";
|
|
9
|
-
import { buildTasks } from "../../services/task-model.mjs";
|
|
10
|
-
import { ok, warn } from "../../render.mjs";
|
|
11
|
-
|
|
12
|
-
export async function startMuxTeam({ sessionId, task, lead, agents, subtasks, layout, hubUrl, teammateMode }) {
|
|
13
|
-
const paneCount = agents.length + 1;
|
|
14
|
-
const effectiveLayout = paneCount <= 4 ? layout : (layout === "Nx1" ? "Nx1" : "1xN");
|
|
15
|
-
console.log(` 레이아웃: ${effectiveLayout} (${paneCount} panes)`);
|
|
16
|
-
|
|
17
|
-
const session = createSession(sessionId, { layout: effectiveLayout, paneCount });
|
|
18
|
-
const leadTarget = session.panes[0];
|
|
19
|
-
startCliInPane(leadTarget, buildCliCommand(lead));
|
|
20
|
-
|
|
21
|
-
const members = [{ role: "lead", name: "lead", cli: lead, pane: leadTarget, agentId: toAgentId(lead, leadTarget) }];
|
|
22
|
-
const assignments = [];
|
|
23
|
-
for (let index = 0; index < agents.length; index += 1) {
|
|
24
|
-
const cli = agents[index];
|
|
25
|
-
const pane = session.panes[index + 1];
|
|
26
|
-
startCliInPane(pane, buildCliCommand(cli));
|
|
27
|
-
const worker = { role: "worker", name: `${cli}-${index + 1}`, cli, pane, subtask: subtasks[index], agentId: toAgentId(cli, pane) };
|
|
28
|
-
members.push(worker);
|
|
29
|
-
assignments.push({ target: pane, cli, subtask: subtasks[index] });
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
ok("CLI 초기화 대기 (3초)...");
|
|
33
|
-
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
34
|
-
await orchestrate(sessionId, assignments, { hubUrl, teammateMode, lead: { target: leadTarget, cli: lead, task } });
|
|
35
|
-
ok("리드/워커 프롬프트 주입 완료");
|
|
36
|
-
|
|
37
|
-
return {
|
|
38
|
-
sessionName: sessionId,
|
|
39
|
-
task,
|
|
40
|
-
lead,
|
|
41
|
-
agents,
|
|
42
|
-
layout: effectiveLayout,
|
|
43
|
-
teammateMode,
|
|
44
|
-
startedAt: Date.now(),
|
|
45
|
-
hubUrl,
|
|
46
|
-
members,
|
|
47
|
-
panes: Object.fromEntries(members.map((member) => [member.pane, {
|
|
48
|
-
role: member.role,
|
|
49
|
-
name: member.name,
|
|
50
|
-
cli: member.cli,
|
|
51
|
-
agentId: member.agentId,
|
|
52
|
-
subtask: member.subtask || null,
|
|
53
|
-
}])),
|
|
54
|
-
tasks: buildTasks(subtasks, members.filter((member) => member.role === "worker")),
|
|
55
|
-
postSave() {
|
|
56
|
-
const profilePrefix = TEAM_PROFILE === "team" ? "" : `TFX_TEAM_PROFILE=${TEAM_PROFILE} `;
|
|
57
|
-
const taskListCommand = `${profilePrefix}${process.execPath} ${join(PKG_ROOT, "bin", "triflux.mjs")} team tasks`;
|
|
58
|
-
configureTeammateKeybindings(sessionId, { inProcess: false, taskListCommand });
|
|
59
|
-
console.log(`\n ${GREEN}${BOLD}팀 세션 준비 완료${RESET}`);
|
|
60
|
-
console.log(` ${DIM}Shift+Down: 다음 팀메이트 전환${RESET}`);
|
|
61
|
-
console.log(` ${DIM}Shift+Tab / Shift+Left: 이전 팀메이트 전환${RESET}`);
|
|
62
|
-
console.log(` ${DIM}Escape: 현재 팀메이트 인터럽트${RESET}`);
|
|
63
|
-
console.log(` ${DIM}Ctrl+T: 태스크 목록${RESET}`);
|
|
64
|
-
console.log(` ${DIM}참고: Shift+Up은 Claude Code 미지원 (scroll-up 충돌). Shift+Tab 사용${RESET}`);
|
|
65
|
-
console.log(` ${DIM}Ctrl+B → D: 세션 분리 (백그라운드)${RESET}\n`);
|
|
66
|
-
if (process.stdout.isTTY && process.stdin.isTTY) attachSession(sessionId);
|
|
67
|
-
else {
|
|
68
|
-
warn("TTY 미지원 환경이라 자동 attach를 생략함");
|
|
69
|
-
console.log(` ${DIM}수동 연결: tfx multi attach${RESET}\n`);
|
|
70
|
-
}
|
|
71
|
-
},
|
|
72
|
-
};
|
|
73
|
-
}
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
|
|
3
|
+
import { buildCliCommand, startCliInPane } from "../../../pane.mjs";
|
|
4
|
+
import { orchestrate } from "../../../orchestrator.mjs";
|
|
5
|
+
import { attachSession, configureTeammateKeybindings, createSession } from "../../../session.mjs";
|
|
6
|
+
import { BOLD, DIM, GREEN, RESET } from "../../../shared.mjs";
|
|
7
|
+
import { toAgentId } from "../../services/member-selector.mjs";
|
|
8
|
+
import { PKG_ROOT, TEAM_PROFILE } from "../../services/state-store.mjs";
|
|
9
|
+
import { buildTasks } from "../../services/task-model.mjs";
|
|
10
|
+
import { ok, warn } from "../../render.mjs";
|
|
11
|
+
|
|
12
|
+
export async function startMuxTeam({ sessionId, task, lead, agents, subtasks, layout, hubUrl, teammateMode }) {
|
|
13
|
+
const paneCount = agents.length + 1;
|
|
14
|
+
const effectiveLayout = paneCount <= 4 ? layout : (layout === "Nx1" ? "Nx1" : "1xN");
|
|
15
|
+
console.log(` 레이아웃: ${effectiveLayout} (${paneCount} panes)`);
|
|
16
|
+
|
|
17
|
+
const session = createSession(sessionId, { layout: effectiveLayout, paneCount });
|
|
18
|
+
const leadTarget = session.panes[0];
|
|
19
|
+
startCliInPane(leadTarget, buildCliCommand(lead));
|
|
20
|
+
|
|
21
|
+
const members = [{ role: "lead", name: "lead", cli: lead, pane: leadTarget, agentId: toAgentId(lead, leadTarget) }];
|
|
22
|
+
const assignments = [];
|
|
23
|
+
for (let index = 0; index < agents.length; index += 1) {
|
|
24
|
+
const cli = agents[index];
|
|
25
|
+
const pane = session.panes[index + 1];
|
|
26
|
+
startCliInPane(pane, buildCliCommand(cli));
|
|
27
|
+
const worker = { role: "worker", name: `${cli}-${index + 1}`, cli, pane, subtask: subtasks[index], agentId: toAgentId(cli, pane) };
|
|
28
|
+
members.push(worker);
|
|
29
|
+
assignments.push({ target: pane, cli, subtask: subtasks[index] });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
ok("CLI 초기화 대기 (3초)...");
|
|
33
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
34
|
+
await orchestrate(sessionId, assignments, { hubUrl, teammateMode, lead: { target: leadTarget, cli: lead, task } });
|
|
35
|
+
ok("리드/워커 프롬프트 주입 완료");
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
sessionName: sessionId,
|
|
39
|
+
task,
|
|
40
|
+
lead,
|
|
41
|
+
agents,
|
|
42
|
+
layout: effectiveLayout,
|
|
43
|
+
teammateMode,
|
|
44
|
+
startedAt: Date.now(),
|
|
45
|
+
hubUrl,
|
|
46
|
+
members,
|
|
47
|
+
panes: Object.fromEntries(members.map((member) => [member.pane, {
|
|
48
|
+
role: member.role,
|
|
49
|
+
name: member.name,
|
|
50
|
+
cli: member.cli,
|
|
51
|
+
agentId: member.agentId,
|
|
52
|
+
subtask: member.subtask || null,
|
|
53
|
+
}])),
|
|
54
|
+
tasks: buildTasks(subtasks, members.filter((member) => member.role === "worker")),
|
|
55
|
+
postSave() {
|
|
56
|
+
const profilePrefix = TEAM_PROFILE === "team" ? "" : `TFX_TEAM_PROFILE=${TEAM_PROFILE} `;
|
|
57
|
+
const taskListCommand = `${profilePrefix}${process.execPath} ${join(PKG_ROOT, "bin", "triflux.mjs")} team tasks`;
|
|
58
|
+
configureTeammateKeybindings(sessionId, { inProcess: false, taskListCommand });
|
|
59
|
+
console.log(`\n ${GREEN}${BOLD}팀 세션 준비 완료${RESET}`);
|
|
60
|
+
console.log(` ${DIM}Shift+Down: 다음 팀메이트 전환${RESET}`);
|
|
61
|
+
console.log(` ${DIM}Shift+Tab / Shift+Left: 이전 팀메이트 전환${RESET}`);
|
|
62
|
+
console.log(` ${DIM}Escape: 현재 팀메이트 인터럽트${RESET}`);
|
|
63
|
+
console.log(` ${DIM}Ctrl+T: 태스크 목록${RESET}`);
|
|
64
|
+
console.log(` ${DIM}참고: Shift+Up은 Claude Code 미지원 (scroll-up 충돌). Shift+Tab 사용${RESET}`);
|
|
65
|
+
console.log(` ${DIM}Ctrl+B → D: 세션 분리 (백그라운드)${RESET}\n`);
|
|
66
|
+
if (process.stdout.isTTY && process.stdin.isTTY) attachSession(sessionId);
|
|
67
|
+
else {
|
|
68
|
+
warn("TTY 미지원 환경이라 자동 attach를 생략함");
|
|
69
|
+
console.log(` ${DIM}수동 연결: tfx multi attach${RESET}\n`);
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|