@triflux/remote 10.1.0 → 10.26.0

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.
Files changed (162) hide show
  1. package/hub/pipe.mjs +239 -121
  2. package/hub/public/dashboard.html +61 -0
  3. package/hub/server.mjs +2179 -709
  4. package/hub/store-adapter.mjs +59 -26
  5. package/hub/store.mjs +356 -153
  6. package/hub/team/agent-map.json +26 -7
  7. package/hub/team/ansi.mjs +245 -146
  8. package/hub/team/backend.mjs +78 -17
  9. package/hub/team/build-worker-prompt.mjs +73 -0
  10. package/hub/team/check-mcp-hub.mjs +49 -0
  11. package/hub/team/claude-daemon-control.mjs +1130 -0
  12. package/hub/team/claude-native-bridge.mjs +1011 -0
  13. package/hub/team/claude-session-projection.mjs +74 -0
  14. package/hub/team/cli/commands/attach.mjs +57 -37
  15. package/hub/team/cli/commands/control.mjs +29 -8
  16. package/hub/team/cli/commands/debug.mjs +95 -74
  17. package/hub/team/cli/commands/focus.mjs +80 -53
  18. package/hub/team/cli/commands/interrupt.mjs +18 -6
  19. package/hub/team/cli/commands/kill.mjs +16 -5
  20. package/hub/team/cli/commands/list.mjs +31 -24
  21. package/hub/team/cli/commands/send.mjs +19 -6
  22. package/hub/team/cli/commands/start/index.mjs +151 -33
  23. package/hub/team/cli/commands/start/parse-args.mjs +97 -13
  24. package/hub/team/cli/commands/start/start-headless.mjs +155 -37
  25. package/hub/team/cli/commands/start/start-in-process.mjs +50 -40
  26. package/hub/team/cli/commands/start/start-mux.mjs +122 -73
  27. package/hub/team/cli/commands/start/start-wt.mjs +86 -69
  28. package/hub/team/cli/commands/status.mjs +43 -14
  29. package/hub/team/cli/commands/stop.mjs +11 -4
  30. package/hub/team/cli/commands/task.mjs +8 -3
  31. package/hub/team/cli/commands/tasks.mjs +13 -13
  32. package/hub/team/cli/help.mjs +1 -1
  33. package/hub/team/cli/index.mjs +2 -2
  34. package/hub/team/cli/manifest.mjs +38 -8
  35. package/hub/team/cli/render.mjs +52 -30
  36. package/hub/team/cli/services/attach-fallback.mjs +74 -54
  37. package/hub/team/cli/services/hub-client.mjs +44 -16
  38. package/hub/team/cli/services/member-selector.mjs +37 -30
  39. package/hub/team/cli/services/native-control.mjs +144 -116
  40. package/hub/team/cli/services/runtime-mode.mjs +61 -4
  41. package/hub/team/cli/services/state-store.mjs +66 -9
  42. package/hub/team/cli/services/task-model.mjs +40 -30
  43. package/hub/team/codex-review.mjs +521 -0
  44. package/hub/team/conductor-mesh-bridge.mjs +25 -24
  45. package/hub/team/conductor-registry.mjs +121 -0
  46. package/hub/team/conductor.mjs +1379 -671
  47. package/hub/team/consensus-meta.mjs +176 -0
  48. package/hub/team/dashboard-anchor.mjs +4 -5
  49. package/hub/team/dashboard-layout.mjs +3 -1
  50. package/hub/team/dashboard-open.mjs +20 -118
  51. package/hub/team/dashboard.mjs +76 -28
  52. package/hub/team/event-log.mjs +18 -10
  53. package/hub/team/execution-mode.mjs +256 -0
  54. package/hub/team/extract-completion-payload.mjs +110 -0
  55. package/hub/team/git-preflight.mjs +286 -0
  56. package/hub/team/handoff.mjs +49 -18
  57. package/hub/team/headless-bridge-session.mjs +17 -0
  58. package/hub/team/headless.mjs +2213 -1156
  59. package/hub/team/health-probe.mjs +232 -69
  60. package/hub/team/interactive-native-launcher.mjs +106 -0
  61. package/hub/team/interactive-native-worker.mjs +80 -0
  62. package/hub/team/interactive-tui-transport.mjs +465 -0
  63. package/hub/team/launcher-template.mjs +49 -17
  64. package/hub/team/lead-control.mjs +91 -4
  65. package/hub/team/mcp-selector.mjs +145 -0
  66. package/hub/team/native-supervisor.mjs +90 -26
  67. package/hub/team/native.mjs +120 -42
  68. package/hub/team/nativeProxy.mjs +184 -113
  69. package/hub/team/notify.mjs +147 -78
  70. package/hub/team/orchestrator.mjs +173 -161
  71. package/hub/team/pane.mjs +43 -20
  72. package/hub/team/process-cleanup.mjs +69 -25
  73. package/hub/team/psmux.mjs +1022 -281
  74. package/hub/team/recovery-store.mjs +149 -0
  75. package/hub/team/remote-probe.mjs +68 -52
  76. package/hub/team/remote-session.mjs +131 -59
  77. package/hub/team/remote-watcher.mjs +61 -33
  78. package/hub/team/retry-state-machine.mjs +336 -0
  79. package/hub/team/routing.mjs +51 -25
  80. package/hub/team/runtime-strategy.mjs +200 -0
  81. package/hub/team/schemas/worker-completion.json +39 -0
  82. package/hub/team/sentinel-capture.mjs +127 -0
  83. package/hub/team/session-sync.mjs +153 -6
  84. package/hub/team/session.mjs +574 -611
  85. package/hub/team/shared.mjs +13 -13
  86. package/hub/team/staleState.mjs +72 -30
  87. package/hub/team/swarm-cli.mjs +390 -0
  88. package/hub/team/swarm-hypervisor.mjs +1544 -193
  89. package/hub/team/swarm-intent.mjs +216 -0
  90. package/hub/team/swarm-locks.mjs +140 -25
  91. package/hub/team/swarm-planner.mjs +121 -21
  92. package/hub/team/swarm-preflight.mjs +319 -0
  93. package/hub/team/swarm-reconciler.mjs +128 -23
  94. package/hub/team/synapse-cli.mjs +458 -0
  95. package/hub/team/synapse-http.mjs +60 -0
  96. package/hub/team/synapse-registry.mjs +329 -0
  97. package/hub/team/terminal-opener.mjs +216 -0
  98. package/hub/team/tui-core.mjs +292 -0
  99. package/hub/team/tui-lite.mjs +237 -148
  100. package/hub/team/tui-remote-adapter.mjs +18 -34
  101. package/hub/team/tui-synapse.mjs +223 -0
  102. package/hub/team/tui-viewer.mjs +108 -48
  103. package/hub/team/tui-widgets.mjs +262 -0
  104. package/hub/team/tui.mjs +931 -439
  105. package/hub/team/worker-completion-validator.mjs +84 -0
  106. package/hub/team/worker-sandbox.mjs +97 -0
  107. package/hub/team/worker-signal.mjs +263 -0
  108. package/hub/team/worker-signal.types.d.ts +52 -0
  109. package/hub/team/worktree-lifecycle.mjs +517 -63
  110. package/hub/team/wt-manager.mjs +297 -26
  111. package/hub/tools.mjs +477 -252
  112. package/hub/tray.mjs +411 -368
  113. package/hub/workers/antigravity-route-worker.mjs +169 -0
  114. package/hub/workers/claude-worker.mjs +162 -118
  115. package/hub/workers/codex-app-server-worker.mjs +1015 -0
  116. package/hub/workers/codex-mcp.mjs +724 -507
  117. package/hub/workers/delegator-mcp.mjs +570 -499
  118. package/hub/workers/factory.mjs +103 -21
  119. package/hub/workers/gemini-worker.mjs +126 -284
  120. package/hub/workers/interface.mjs +24 -2
  121. package/hub/workers/lib/jsonrpc-stdio.mjs +463 -0
  122. package/hub/workers/worker-utils.mjs +23 -17
  123. package/package.json +4 -3
  124. package/scripts/lib/agent-json.mjs +27 -0
  125. package/scripts/lib/async.mjs +174 -0
  126. package/scripts/lib/claudemd-scanner.mjs +223 -0
  127. package/scripts/lib/cli-agy.mjs +62 -0
  128. package/scripts/lib/cli-claude.mjs +78 -0
  129. package/scripts/lib/cli-codex.mjs +199 -0
  130. package/scripts/lib/cli-gemini.mjs +37 -0
  131. package/scripts/lib/codex-recovery.sh +50 -0
  132. package/scripts/lib/context.mjs +67 -0
  133. package/scripts/lib/cross-review-utils.mjs +59 -0
  134. package/scripts/lib/doctor-env-checks.mjs +121 -0
  135. package/scripts/lib/dynamic-route-cli.mjs +107 -0
  136. package/scripts/lib/env-probe.mjs +269 -0
  137. package/scripts/lib/env.mjs +14 -0
  138. package/scripts/lib/gemini-profiles.mjs +119 -0
  139. package/scripts/lib/handoff.mjs +187 -0
  140. package/scripts/lib/hook-utils.mjs +16 -0
  141. package/scripts/lib/hub.mjs +48 -0
  142. package/scripts/lib/keyword-rules.mjs +195 -0
  143. package/scripts/lib/logger.mjs +105 -0
  144. package/scripts/lib/mcp-filter.mjs +883 -0
  145. package/scripts/lib/mcp-gateway-health-check.mjs +159 -0
  146. package/scripts/lib/mcp-gateway-servers.mjs +70 -0
  147. package/scripts/lib/mcp-gateway-wrapper-check.mjs +35 -0
  148. package/scripts/lib/mcp-guard-engine.mjs +2120 -0
  149. package/scripts/lib/mcp-health.mjs +773 -0
  150. package/scripts/lib/mcp-manifest.mjs +89 -0
  151. package/scripts/lib/mcp-server-catalog.mjs +355 -0
  152. package/scripts/lib/pid.mjs +63 -0
  153. package/scripts/lib/process-utils.mjs +26 -0
  154. package/scripts/lib/psmux-info.mjs +224 -0
  155. package/scripts/lib/quota.mjs +47 -0
  156. package/scripts/lib/remote-spawn-transfer.mjs +226 -0
  157. package/scripts/lib/skill-state.mjs +220 -0
  158. package/scripts/lib/skill-template.mjs +375 -0
  159. package/scripts/lib/team.mjs +75 -0
  160. package/scripts/lib/timeout.mjs +66 -0
  161. package/scripts/lib/tmp.mjs +26 -0
  162. package/scripts/lib/toml.mjs +113 -0
@@ -0,0 +1,74 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+
4
+ export function normalizeClaudeBridgeSessionId(bridgeSessionId, short) {
5
+ const value = String(bridgeSessionId || "").trim();
6
+ if (value.startsWith("session_")) return value;
7
+ if (value.startsWith("cse_")) return `session_${value.slice(4)}`;
8
+ if (value) return value;
9
+ return `session_${short}`;
10
+ }
11
+
12
+ export function buildClaudeSessionProjection({
13
+ pid,
14
+ procStart,
15
+ sessionId,
16
+ short,
17
+ cwd,
18
+ name,
19
+ agent,
20
+ startedAt = Date.now(),
21
+ updatedAt = Date.now(),
22
+ status = "busy",
23
+ version = "triflux-native-bridge",
24
+ bridgeSessionId,
25
+ } = {}) {
26
+ if (!Number.isInteger(pid) || pid <= 0) throw new Error("pid is required");
27
+ if (!procStart) throw new Error("procStart is required");
28
+ if (!sessionId) throw new Error("sessionId is required");
29
+ if (!short) throw new Error("short is required");
30
+ if (!cwd) throw new Error("cwd is required");
31
+ if (!name) throw new Error("name is required");
32
+ if (!agent) throw new Error("agent is required");
33
+ return {
34
+ pid,
35
+ sessionId,
36
+ cwd,
37
+ startedAt,
38
+ procStart,
39
+ version,
40
+ peerProtocol: 1,
41
+ kind: "bg",
42
+ entrypoint: "cli",
43
+ name,
44
+ agent,
45
+ jobId: short,
46
+ status,
47
+ updatedAt,
48
+ bridgeSessionId: normalizeClaudeBridgeSessionId(bridgeSessionId, short),
49
+ };
50
+ }
51
+
52
+ export async function writeClaudeSessionProjection(sessionsDir, projection) {
53
+ await fs.mkdir(sessionsDir, { recursive: true });
54
+ const sessionPath = path.join(sessionsDir, `${projection.pid}.json`);
55
+ const tmpPath = `${sessionPath}.${process.pid}.tmp`;
56
+ await fs.writeFile(tmpPath, `${JSON.stringify(projection)}\n`, "utf8");
57
+ await fs.rename(tmpPath, sessionPath);
58
+ return sessionPath;
59
+ }
60
+
61
+ export async function updateClaudeSessionProjection(sessionPath, patch) {
62
+ const parsed = JSON.parse(await fs.readFile(sessionPath, "utf8"));
63
+ const next = {
64
+ ...parsed,
65
+ ...patch,
66
+ updatedAt: patch.updatedAt || Date.now(),
67
+ };
68
+ await fs.writeFile(sessionPath, `${JSON.stringify(next)}\n`, "utf8");
69
+ return next;
70
+ }
71
+
72
+ export async function removeClaudeSessionProjection(sessionPath) {
73
+ await fs.rm(sessionPath, { force: true });
74
+ }
@@ -1,37 +1,57 @@
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 { fail, ok, warn } from "../render.mjs";
4
+ import {
5
+ buildManualAttachCommand,
6
+ launchAttachInWindowsTerminal,
7
+ wantsWtAttachFallback,
8
+ } from "../services/attach-fallback.mjs";
9
+ import {
10
+ isNativeMode,
11
+ isTeamAlive,
12
+ isWtMode,
13
+ } from "../services/runtime-mode.mjs";
14
+ import { loadTeamState } from "../services/state-store.mjs";
15
+
16
+ export async function teamAttach(args = []) {
17
+ const state = loadTeamState();
18
+ if (!state || !isTeamAlive(state)) {
19
+ console.log(`\n ${DIM}활성 팀 세션 없음${RESET}\n`);
20
+ return;
21
+ }
22
+ if (isNativeMode(state)) {
23
+ console.log(
24
+ `\n ${DIM}in-process 모드는 별도 attach가 없습니다.${RESET}\n ${DIM}상태 확인: tfx multi status${RESET}\n`,
25
+ );
26
+ return;
27
+ }
28
+ if (isWtMode(state)) {
29
+ console.log(
30
+ `\n ${DIM}wt 모드는 attach 개념이 없습니다 (Windows Terminal pane가 독립 실행됨).${RESET}\n ${DIM}재실행/정리는: tfx multi stop${RESET}\n`,
31
+ );
32
+ return;
33
+ }
34
+
35
+ try {
36
+ attachSession(state.sessionName);
37
+ } catch (error) {
38
+ const allowWt = wantsWtAttachFallback(args);
39
+ if (allowWt && (await launchAttachInWindowsTerminal(state.sessionName))) {
40
+ warn(`현재 터미널에서 attach 실패: ${error.message}`);
41
+ ok("Windows Terminal split-pane로 attach 재시도 창을 열었습니다.");
42
+ console.log(
43
+ ` ${DIM}수동 attach 명령: ${buildManualAttachCommand(state.sessionName)}${RESET}\n`,
44
+ );
45
+ return;
46
+ }
47
+ fail(`attach 실패: ${error.message}`);
48
+ warn(
49
+ allowWt
50
+ ? "WT 분할창 attach 자동 검증 실패 (session_attached 증가 없음)"
51
+ : "자동 WT 분할은 기본 비활성입니다. 필요 시 --wt 옵션으로 실행하세요.",
52
+ );
53
+ console.log(
54
+ ` ${DIM}수동 attach 명령: ${buildManualAttachCommand(state.sessionName)}${RESET}\n`,
55
+ );
56
+ }
57
+ }
@@ -1,11 +1,15 @@
1
1
  import { injectPrompt, sendKeys } from "../../pane.mjs";
2
2
  import { DIM, RESET, WHITE, YELLOW } from "../../shared.mjs";
3
+ import { ok, warn } from "../render.mjs";
3
4
  import { publishLeadControl } from "../services/hub-client.mjs";
4
5
  import { resolveMember } from "../services/member-selector.mjs";
5
6
  import { nativeRequest } from "../services/native-control.mjs";
6
- import { isNativeMode, isTeamAlive, isWtMode } from "../services/runtime-mode.mjs";
7
+ import {
8
+ isNativeMode,
9
+ isTeamAlive,
10
+ isWtMode,
11
+ } from "../services/runtime-mode.mjs";
7
12
  import { loadTeamState } from "../services/state-store.mjs";
8
- import { ok, warn } from "../render.mjs";
9
13
 
10
14
  export async function teamControl(args = []) {
11
15
  const state = loadTeamState();
@@ -17,26 +21,43 @@ export async function teamControl(args = []) {
17
21
  const member = resolveMember(state, args[0]);
18
22
  const command = String(args[1] || "").toLowerCase();
19
23
  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`);
24
+ if (
25
+ !member ||
26
+ !new Set(["interrupt", "stop", "pause", "resume"]).has(command)
27
+ ) {
28
+ console.log(
29
+ `\n 사용법: ${WHITE}tfx multi control <lead|이름|번호> <interrupt|stop|pause|resume> [사유]${RESET}\n`,
30
+ );
22
31
  return;
23
32
  }
24
33
  if (isWtMode(state)) {
25
- console.log(`\n ${YELLOW}⚠${RESET} wt 모드는 Hub direct/control 주입 경로가 비활성입니다.\n ${DIM}수동 제어: 해당 pane에서 직접 명령/인터럽트를 수행하세요.${RESET}\n`);
34
+ console.log(
35
+ `\n ${YELLOW}⚠${RESET} wt 모드는 Hub direct/control 주입 경로가 비활성입니다.\n ${DIM}수동 제어: 해당 pane에서 직접 명령/인터럽트를 수행하세요.${RESET}\n`,
36
+ );
26
37
  return;
27
38
  }
28
39
 
29
40
  let directOk = false;
30
41
  if (isNativeMode(state)) {
31
- directOk = !!(await nativeRequest(state, "/control", { member: member.name, command, reason }))?.ok;
42
+ directOk = !!(
43
+ await nativeRequest(state, "/control", {
44
+ member: member.name,
45
+ command,
46
+ reason,
47
+ })
48
+ )?.ok;
32
49
  } else {
33
- injectPrompt(member.pane, `[LEAD CONTROL] command=${command}${reason ? ` reason=${reason}` : ""}`);
50
+ injectPrompt(
51
+ member.pane,
52
+ `[LEAD CONTROL] command=${command}${reason ? ` reason=${reason}` : ""}`,
53
+ );
34
54
  if (command === "interrupt") sendKeys(member.pane, "C-c");
35
55
  directOk = true;
36
56
  }
37
57
 
38
58
  const published = await publishLeadControl(state, member, command, reason);
39
- if (directOk && published) ok(`${member.name} 제어 전송 (${command}, direct + hub)`);
59
+ if (directOk && published)
60
+ ok(`${member.name} 제어 전송 (${command}, direct + hub)`);
40
61
  else if (directOk) ok(`${member.name} 제어 전송 (${command}, direct only)`);
41
62
  else warn(`${member.name} 제어 전송 실패 (${command})`);
42
63
  console.log("");
@@ -1,74 +1,95 @@
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 {
2
+ capturePaneOutput,
3
+ detectMultiplexer,
4
+ getSessionAttachedCount,
5
+ hasWindowsTerminal,
6
+ hasWindowsTerminalSession,
7
+ listSessions,
8
+ } from "../../session.mjs";
9
+ import { AMBER, BOLD, DIM, RESET } from "../../shared.mjs";
10
+ import { formatCompletionSuffix } from "../render.mjs";
11
+ import { getHubInfo, nativeGetStatus } from "../services/hub-client.mjs";
12
+ import {
13
+ isNativeMode,
14
+ isTeamAlive,
15
+ isWtMode,
16
+ } from "../services/runtime-mode.mjs";
17
+ import { loadTeamState, TEAM_PROFILE } from "../services/state-store.mjs";
18
+
19
+ export async function teamDebug(args = []) {
20
+ const state = loadTeamState();
21
+ const flagIndex = args.findIndex((arg) => arg === "--lines" || arg === "-n");
22
+ const lines =
23
+ flagIndex === -1
24
+ ? 20
25
+ : Math.max(3, parseInt(args[flagIndex + 1] || "20", 10) || 20);
26
+ const hub = await getHubInfo();
27
+
28
+ console.log(`\n ${AMBER}${BOLD}⬡ Team Debug${RESET}\n`);
29
+ console.log(` platform: ${process.platform}`);
30
+ console.log(` node: ${process.version}`);
31
+ console.log(
32
+ ` tty: stdout=${!!process.stdout.isTTY}, stdin=${!!process.stdin.isTTY}`,
33
+ );
34
+ console.log(` mux: ${detectMultiplexer() || "none"}`);
35
+ console.log(` hub-pid: ${hub ? `${hub.pid}` : "-"}`);
36
+ console.log(` hub-url: ${hub?.url || "-"}`);
37
+ const sessions = listSessions();
38
+ console.log(` sessions: ${sessions.length ? sessions.join(", ") : "-"}`);
39
+
40
+ if (!state) {
41
+ console.log(`\n ${DIM}team-state 없음 (활성 세션 없음)${RESET}\n`);
42
+ return;
43
+ }
44
+
45
+ console.log(`\n ${BOLD}state${RESET}`);
46
+ console.log(` session: ${state.sessionName}`);
47
+ console.log(` profile: ${state.profile || TEAM_PROFILE}`);
48
+ console.log(` mode: ${state.teammateMode || "tmux"}`);
49
+ console.log(` lead: ${state.lead}`);
50
+ console.log(` agents: ${(state.agents || []).join(", ")}`);
51
+ console.log(` alive: ${isTeamAlive(state) ? "yes" : "no"}`);
52
+ console.log(
53
+ ` attached: ${getSessionAttachedCount(state.sessionName) ?? "-"}`,
54
+ );
55
+
56
+ if (isWtMode(state)) {
57
+ console.log(`\n ${BOLD}wt-session${RESET}`);
58
+ console.log(` window: ${state?.wt?.windowId ?? 0}`);
59
+ console.log(` layout: ${state?.wt?.layout || state?.layout || "-"}`);
60
+ console.log(
61
+ ` panes: ${state?.wt?.paneCount ?? (state.members || []).length}`,
62
+ );
63
+ console.log(` wt.exe: ${hasWindowsTerminal() ? "yes" : "no"}`);
64
+ console.log(` WT_SESSION:${hasWindowsTerminalSession() ? "yes" : "no"}`);
65
+ console.log("");
66
+ return;
67
+ }
68
+
69
+ if (isNativeMode(state)) {
70
+ console.log(`\n ${BOLD}native-members${RESET}`);
71
+ const members = (await nativeGetStatus(state))?.data?.members || [];
72
+ if (!members.length) console.log(` ${DIM}(no data)${RESET}`);
73
+ for (const member of members)
74
+ console.log(
75
+ ` - ${member.name}: ${member.status}${formatCompletionSuffix(member)}${member.lastPreview ? ` ${DIM}${member.lastPreview}${RESET}` : ""}`,
76
+ );
77
+ console.log("");
78
+ return;
79
+ }
80
+
81
+ console.log(
82
+ `\n ${BOLD}pane-tail${RESET} ${DIM}(last ${lines} lines)${RESET}`,
83
+ );
84
+ if (!(state.members || []).length)
85
+ console.log(` ${DIM}(members 없음)${RESET}`);
86
+ for (const member of state.members || []) {
87
+ console.log(`\n [${member.name}] ${member.pane}`);
88
+ for (const line of (capturePaneOutput(member.pane, lines) || "(empty)")
89
+ .split("\n")
90
+ .slice(-lines)) {
91
+ console.log(` ${line}`);
92
+ }
93
+ }
94
+ console.log("");
95
+ }
@@ -1,53 +1,80 @@
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 { fail, ok, warn } from "../render.mjs";
4
+ import {
5
+ buildManualAttachCommand,
6
+ launchAttachInWindowsTerminal,
7
+ wantsWtAttachFallback,
8
+ } from "../services/attach-fallback.mjs";
9
+ import { resolveMember } from "../services/member-selector.mjs";
10
+ import {
11
+ isNativeMode,
12
+ isTeamAlive,
13
+ isWtMode,
14
+ } from "../services/runtime-mode.mjs";
15
+ import { loadTeamState } from "../services/state-store.mjs";
16
+
17
+ export async function teamFocus(args = []) {
18
+ const state = loadTeamState();
19
+ if (!state || !isTeamAlive(state)) {
20
+ console.log(`\n ${DIM}활성 팀 세션 없음${RESET}\n`);
21
+ return;
22
+ }
23
+ if (isNativeMode(state)) {
24
+ console.log(
25
+ `\n ${DIM}in-process 모드는 focus/attach 개념이 없습니다.${RESET}\n ${DIM}직접 지시: tfx multi send <대상> "메시지"${RESET}\n`,
26
+ );
27
+ return;
28
+ }
29
+
30
+ const member = resolveMember(state, args[0]);
31
+ if (!member) {
32
+ console.log(
33
+ `\n 사용법: ${WHITE}tfx multi focus <lead|이름|번호>${RESET}\n`,
34
+ );
35
+ return;
36
+ }
37
+
38
+ if (isWtMode(state)) {
39
+ const paneIndex = Number(/^wt:(\d+)$/.exec(member.pane || "")?.[1]);
40
+ if (!Number.isFinite(paneIndex)) {
41
+ console.log(
42
+ `\n ${DIM}wt pane 인덱스 파싱 실패: ${member.pane}${RESET}\n`,
43
+ );
44
+ return;
45
+ }
46
+ if (
47
+ focusWtPane(paneIndex, {
48
+ layout: state?.wt?.layout || state?.layout || "1xN",
49
+ })
50
+ )
51
+ ok(`${member.name} pane 포커스 이동 (wt)`);
52
+ else warn("wt pane 포커스 이동 실패 (WT_SESSION/wt.exe 상태 확인 필요)");
53
+ console.log("");
54
+ return;
55
+ }
56
+
57
+ focusPane(member.pane, { zoom: false });
58
+ try {
59
+ attachSession(state.sessionName);
60
+ } catch (error) {
61
+ const allowWt = wantsWtAttachFallback(args);
62
+ if (allowWt && (await launchAttachInWindowsTerminal(state.sessionName))) {
63
+ warn(`현재 터미널에서 attach 실패: ${error.message}`);
64
+ ok("Windows Terminal split-pane로 attach 재시도 창을 열었습니다.");
65
+ console.log(
66
+ ` ${DIM}수동 attach 명령: ${buildManualAttachCommand(state.sessionName)}${RESET}\n`,
67
+ );
68
+ return;
69
+ }
70
+ fail(`attach 실패: ${error.message}`);
71
+ warn(
72
+ allowWt
73
+ ? "WT 분할창 attach 자동 검증 실패 (session_attached 증가 없음)"
74
+ : "자동 WT 분할은 기본 비활성입니다. 필요 시 --wt 옵션으로 실행하세요.",
75
+ );
76
+ console.log(
77
+ ` ${DIM}수동 attach 명령: ${buildManualAttachCommand(state.sessionName)}${RESET}\n`,
78
+ );
79
+ }
80
+ }
@@ -1,10 +1,14 @@
1
1
  import { sendKeys } from "../../pane.mjs";
2
2
  import { DIM, RESET, WHITE, YELLOW } from "../../shared.mjs";
3
+ import { ok, warn } from "../render.mjs";
3
4
  import { resolveMember } from "../services/member-selector.mjs";
4
5
  import { nativeRequest } from "../services/native-control.mjs";
5
- import { isNativeMode, isTeamAlive, isWtMode } from "../services/runtime-mode.mjs";
6
+ import {
7
+ isNativeMode,
8
+ isTeamAlive,
9
+ isWtMode,
10
+ } from "../services/runtime-mode.mjs";
6
11
  import { loadTeamState } from "../services/state-store.mjs";
7
- import { ok, warn } from "../render.mjs";
8
12
 
9
13
  export async function teamInterrupt(args = []) {
10
14
  const state = loadTeamState();
@@ -15,17 +19,25 @@ export async function teamInterrupt(args = []) {
15
19
 
16
20
  const member = resolveMember(state, args[0] || "lead");
17
21
  if (!member) {
18
- console.log(`\n 사용법: ${WHITE}tfx multi interrupt <lead|이름|번호>${RESET}\n`);
22
+ console.log(
23
+ `\n 사용법: ${WHITE}tfx multi interrupt <lead|이름|번호>${RESET}\n`,
24
+ );
19
25
  return;
20
26
  }
21
27
  if (isWtMode(state)) {
22
- console.log(`\n ${YELLOW}⚠${RESET} wt 모드에서는 pane stdin 주입이 지원되지 않아 interrupt를 자동 전송할 수 없습니다.\n ${DIM}수동으로 해당 pane에서 Ctrl+C를 입력하세요.${RESET}\n`);
28
+ console.log(
29
+ `\n ${YELLOW}⚠${RESET} wt 모드에서는 pane stdin 주입이 지원되지 않아 interrupt를 자동 전송할 수 없습니다.\n ${DIM}수동으로 해당 pane에서 Ctrl+C를 입력하세요.${RESET}\n`,
30
+ );
23
31
  return;
24
32
  }
25
33
 
26
34
  if (isNativeMode(state)) {
27
- const result = await nativeRequest(state, "/interrupt", { member: member.name });
28
- (result?.ok ? ok : warn)(`${member.name} ${result?.ok ? "인터럽트 전송" : "인터럽트 실패"}`);
35
+ const result = await nativeRequest(state, "/interrupt", {
36
+ member: member.name,
37
+ });
38
+ (result?.ok ? ok : warn)(
39
+ `${member.name} ${result?.ok ? "인터럽트 전송" : "인터럽트 실패"}`,
40
+ );
29
41
  console.log("");
30
42
  return;
31
43
  }
@@ -1,24 +1,35 @@
1
1
  import { closeWtSession, killSession, listSessions } from "../../session.mjs";
2
2
  import { DIM, RESET } from "../../shared.mjs";
3
+ import { ok } from "../render.mjs";
3
4
  import { nativeRequest } from "../services/native-control.mjs";
4
- import { isNativeMode, isTeamAlive, isWtMode } from "../services/runtime-mode.mjs";
5
+ import {
6
+ isNativeMode,
7
+ isTeamAlive,
8
+ isWtMode,
9
+ } from "../services/runtime-mode.mjs";
5
10
  import { clearTeamState, loadTeamState } from "../services/state-store.mjs";
6
- import { ok } from "../render.mjs";
7
11
 
8
12
  export async function teamKill() {
9
13
  const state = loadTeamState();
10
14
  if (state && isNativeMode(state) && isTeamAlive(state)) {
11
15
  await nativeRequest(state, "/stop", {});
12
- try { process.kill(state.native.supervisorPid, "SIGTERM"); } catch {}
16
+ try {
17
+ process.kill(state.native.supervisorPid, "SIGTERM");
18
+ } catch {}
13
19
  clearTeamState(state.sessionId);
14
20
  ok(`종료: ${state.sessionName}`);
15
21
  console.log("");
16
22
  return;
17
23
  }
18
24
  if (state && isWtMode(state)) {
19
- const closed = closeWtSession({ layout: state?.wt?.layout || state?.layout || "1xN", paneCount: state?.wt?.paneCount ?? (state.members || []).length });
25
+ const closed = closeWtSession({
26
+ layout: state?.wt?.layout || state?.layout || "1xN",
27
+ paneCount: state?.wt?.paneCount ?? (state.members || []).length,
28
+ });
20
29
  clearTeamState(state.sessionId);
21
- ok(`종료: ${state.sessionName}${closed ? ` (${closed} panes closed)` : ""}`);
30
+ ok(
31
+ `종료: ${state.sessionName}${closed ? ` (${closed} panes closed)` : ""}`,
32
+ );
22
33
  console.log("");
23
34
  return;
24
35
  }