@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
@@ -1,69 +1,86 @@
1
- import { createWtSession } from "../../../session.mjs";
2
- import { buildCliCommand } from "../../../pane.mjs";
3
- import { toAgentId } from "../../services/member-selector.mjs";
4
- import { buildTasks } from "../../services/task-model.mjs";
5
- import { warn } from "../../render.mjs";
6
-
7
- export async function startWtTeam({ sessionId, task, lead, agents, subtasks, layout, hubUrl }) {
8
- const paneCount = agents.length + 1;
9
- const effectiveLayout = layout === "Nx1" ? "Nx1" : "1xN";
10
- if (layout !== effectiveLayout) warn(`wt 모드에서 ${layout} 레이아웃은 미지원 — ${effectiveLayout}로 대체`);
11
- console.log(` 레이아웃: ${effectiveLayout} (${paneCount} panes)`);
12
-
13
- const session = createWtSession(sessionId, {
14
- layout: effectiveLayout,
15
- paneCommands: [
16
- { title: `${sessionId}-lead`, command: buildCliCommand(lead) },
17
- ...agents.map((cli, index) => ({
18
- title: `${sessionId}-${cli}-${index + 1}`,
19
- command: buildCliCommand(cli),
20
- })),
21
- ],
22
- });
23
-
24
- const members = [
25
- {
26
- role: "lead",
27
- name: "lead",
28
- cli: lead,
29
- pane: session.panes[0] || "wt:0",
30
- agentId: toAgentId(lead, session.panes[0] || "wt:0"),
31
- },
32
- ...agents.map((cli, index) => {
33
- const pane = session.panes[index + 1] || `wt:${index + 1}`;
34
- return {
35
- role: "worker",
36
- name: `${cli}-${index + 1}`,
37
- cli,
38
- pane,
39
- subtask: subtasks[index],
40
- agentId: toAgentId(cli, pane),
41
- };
42
- }),
43
- ];
44
-
45
- return {
46
- sessionName: sessionId,
47
- task,
48
- lead,
49
- agents,
50
- layout: effectiveLayout,
51
- teammateMode: "wt",
52
- startedAt: Date.now(),
53
- hubUrl,
54
- members,
55
- panes: Object.fromEntries(members.map((member) => [member.pane, {
56
- role: member.role,
57
- name: member.name,
58
- cli: member.cli,
59
- agentId: member.agentId,
60
- subtask: member.subtask || null,
61
- }])),
62
- tasks: buildTasks(subtasks, members.filter((member) => member.role === "worker")),
63
- wt: {
64
- windowId: 0,
65
- layout: effectiveLayout,
66
- paneCount: session.paneCount,
67
- },
68
- };
69
- }
1
+ import { buildCliCommand } from "../../../pane.mjs";
2
+ import { createWtSession } from "../../../session.mjs";
3
+ import { warn } from "../../render.mjs";
4
+ import { toAgentId } from "../../services/member-selector.mjs";
5
+ import { buildTasks } from "../../services/task-model.mjs";
6
+
7
+ export async function startWtTeam({
8
+ sessionId,
9
+ task,
10
+ lead,
11
+ agents,
12
+ subtasks,
13
+ layout,
14
+ hubUrl,
15
+ }) {
16
+ const paneCount = agents.length + 1;
17
+ const effectiveLayout = layout === "Nx1" ? "Nx1" : "1xN";
18
+ if (layout !== effectiveLayout)
19
+ warn(`wt 모드에서 ${layout} 레이아웃은 미지원 — ${effectiveLayout}로 대체`);
20
+ console.log(` 레이아웃: ${effectiveLayout} (${paneCount} panes)`);
21
+
22
+ const session = await createWtSession(sessionId, {
23
+ layout: effectiveLayout,
24
+ paneCommands: [
25
+ { title: `${sessionId}-lead`, command: buildCliCommand(lead) },
26
+ ...agents.map((cli, index) => ({
27
+ title: `${sessionId}-${cli}-${index + 1}`,
28
+ command: buildCliCommand(cli),
29
+ })),
30
+ ],
31
+ });
32
+
33
+ const members = [
34
+ {
35
+ role: "lead",
36
+ name: "lead",
37
+ cli: lead,
38
+ pane: session.panes[0] || "wt:0",
39
+ agentId: toAgentId(lead, session.panes[0] || "wt:0"),
40
+ },
41
+ ...agents.map((cli, index) => {
42
+ const pane = session.panes[index + 1] || `wt:${index + 1}`;
43
+ return {
44
+ role: "worker",
45
+ name: `${cli}-${index + 1}`,
46
+ cli,
47
+ pane,
48
+ subtask: subtasks[index],
49
+ agentId: toAgentId(cli, pane),
50
+ };
51
+ }),
52
+ ];
53
+
54
+ return {
55
+ sessionName: sessionId,
56
+ task,
57
+ lead,
58
+ agents,
59
+ layout: effectiveLayout,
60
+ teammateMode: "wt",
61
+ startedAt: Date.now(),
62
+ hubUrl,
63
+ members,
64
+ panes: Object.fromEntries(
65
+ members.map((member) => [
66
+ member.pane,
67
+ {
68
+ role: member.role,
69
+ name: member.name,
70
+ cli: member.cli,
71
+ agentId: member.agentId,
72
+ subtask: member.subtask || null,
73
+ },
74
+ ]),
75
+ ),
76
+ tasks: buildTasks(
77
+ subtasks,
78
+ members.filter((member) => member.role === "worker"),
79
+ ),
80
+ wt: {
81
+ windowId: 0,
82
+ layout: effectiveLayout,
83
+ paneCount: session.paneCount,
84
+ },
85
+ };
86
+ }
@@ -1,16 +1,22 @@
1
- import { AMBER, BOLD, DIM, GRAY, GREEN, RED, RESET } from "../../shared.mjs";
2
1
  import { hasWindowsTerminalSession } from "../../session.mjs";
2
+ import { AMBER, BOLD, DIM, GRAY, GREEN, RED, RESET } from "../../shared.mjs";
3
+ import { formatCompletionSuffix } from "../render.mjs";
3
4
  import { fetchHubTaskList, nativeGetStatus } from "../services/hub-client.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 { loadTeamState } from "../services/state-store.mjs";
6
- import { formatCompletionSuffix } from "../render.mjs";
7
11
 
8
12
  export async function teamStatus(args = []) {
9
13
  const json = process.env.TFX_OUTPUT_JSON === "1" || args.includes("--json");
10
14
  const state = loadTeamState();
11
15
  if (!state) {
12
16
  if (json) {
13
- process.stdout.write(`${JSON.stringify({ status: "offline", sessionName: null, alive: false }, null, 2)}\n`);
17
+ process.stdout.write(
18
+ `${JSON.stringify({ status: "offline", sessionName: null, alive: false }, null, 2)}\n`,
19
+ );
14
20
  return;
15
21
  }
16
22
  console.log(`\n ${DIM}활성 팀 세션 없음${RESET}\n`);
@@ -48,37 +54,60 @@ export async function teamStatus(args = []) {
48
54
  return;
49
55
  }
50
56
 
51
- console.log(`\n ${AMBER}${BOLD}⬡ tfx multi${RESET} ${alive ? `${GREEN}● active${RESET}` : `${RED}● dead${RESET}`}\n`);
57
+ console.log(
58
+ `\n ${AMBER}${BOLD}⬡ tfx multi${RESET} ${alive ? `${GREEN}● active${RESET}` : `${RED}● dead${RESET}`}\n`,
59
+ );
52
60
  console.log(` 세션: ${state.sessionName}`);
53
61
  console.log(` 모드: ${state.teammateMode || "tmux"}`);
54
62
  console.log(` 리드: ${state.lead || "claude"}`);
55
63
  console.log(` 워커: ${(state.agents || []).join(", ")}`);
56
- console.log(` Uptime: ${alive ? `${Math.round((Date.now() - state.startedAt) / 60000)}분` : "-"}`);
64
+ console.log(
65
+ ` Uptime: ${alive ? `${Math.round((Date.now() - state.startedAt) / 60000)}분` : "-"}`,
66
+ );
57
67
  console.log(` 태스크: ${(state.tasks || []).length}`);
58
68
  if (isWtMode(state) && !hasWindowsTerminalSession()) {
59
- console.log(` ${DIM}WT_SESSION 미감지: 생존성은 heuristics로 판정됨${RESET}`);
69
+ console.log(
70
+ ` ${DIM}WT_SESSION 미감지: 생존성은 heuristics로 판정됨${RESET}`,
71
+ );
60
72
  }
61
73
 
62
74
  for (const member of state.members || []) {
63
- console.log(` - ${member.name} (${member.cli}) ${DIM}${member.role}${RESET} ${DIM}${member.pane}${RESET}`);
75
+ console.log(
76
+ ` - ${member.name} (${member.cli}) ${DIM}${member.role}${RESET} ${DIM}${member.pane}${RESET}`,
77
+ );
64
78
  }
65
79
 
66
80
  if (isNativeMode(state) && alive) {
67
81
  for (const member of payload.nativeMembers) {
68
- console.log(` • ${member.name}: ${member.status}${formatCompletionSuffix(member)}${member.lastPreview ? ` ${DIM}${member.lastPreview}${RESET}` : ""}`);
82
+ console.log(
83
+ ` • ${member.name}: ${member.status}${formatCompletionSuffix(member)}${member.lastPreview ? ` ${DIM}${member.lastPreview}${RESET}` : ""}`,
84
+ );
69
85
  }
70
86
  }
71
87
 
72
88
  if (alive) {
73
- const hubTasks = payload.hubTasks || await fetchHubTaskList(state);
89
+ const hubTasks = payload.hubTasks || (await fetchHubTaskList(state));
74
90
  if (hubTasks.length) {
75
- const completed = hubTasks.filter((task) => task.status === "completed").length;
91
+ const completed = hubTasks.filter(
92
+ (task) => task.status === "completed",
93
+ ).length;
76
94
  const failed = hubTasks.filter((task) => task.status === "failed").length;
77
- console.log(`\n ${BOLD}Hub Tasks${RESET} ${DIM}(${completed}/${hubTasks.length} done)${RESET}`);
95
+ console.log(
96
+ `\n ${BOLD}Hub Tasks${RESET} ${DIM}(${completed}/${hubTasks.length} done)${RESET}`,
97
+ );
78
98
  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}`;
99
+ const icon =
100
+ task.status === "completed"
101
+ ? `${GREEN}✓${RESET}`
102
+ : task.status === "in_progress"
103
+ ? `${AMBER}●${RESET}`
104
+ : task.status === "failed"
105
+ ? `${RED}✗${RESET}`
106
+ : `${GRAY}○${RESET}`;
80
107
  const owner = task.owner ? ` ${GRAY}[${task.owner}]${RESET}` : "";
81
- console.log(` ${icon} ${task.subject || task.description?.slice(0, 50) || ""}${owner}`);
108
+ console.log(
109
+ ` ${icon} ${task.subject || task.description?.slice(0, 50) || ""}${owner}`,
110
+ );
82
111
  }
83
112
  if (failed > 0) console.log(` ${RED}⚠ ${failed}건 실패${RESET}`);
84
113
  }
@@ -1,9 +1,9 @@
1
1
  import { closeWtSession, killSession, sessionExists } 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
5
  import { isNativeMode, isWtMode } from "../services/runtime-mode.mjs";
5
6
  import { clearTeamState, loadTeamState } from "../services/state-store.mjs";
6
- import { ok } from "../render.mjs";
7
7
 
8
8
  export async function teamStop() {
9
9
  const state = loadTeamState();
@@ -14,11 +14,18 @@ export async function teamStop() {
14
14
 
15
15
  if (isNativeMode(state)) {
16
16
  await nativeRequest(state, "/stop", {});
17
- try { process.kill(state.native.supervisorPid, "SIGTERM"); } catch {}
17
+ try {
18
+ process.kill(state.native.supervisorPid, "SIGTERM");
19
+ } catch {}
18
20
  ok(`세션 종료: ${state.sessionName}`);
19
21
  } else if (isWtMode(state)) {
20
- const closed = closeWtSession({ layout: state?.wt?.layout || state?.layout || "1xN", paneCount: state?.wt?.paneCount ?? (state.members || []).length });
21
- ok(`세션 종료: ${state.sessionName}${closed ? ` (${closed} panes closed)` : ""}`);
22
+ const closed = closeWtSession({
23
+ layout: state?.wt?.layout || state?.layout || "1xN",
24
+ paneCount: state?.wt?.paneCount ?? (state.members || []).length,
25
+ });
26
+ ok(
27
+ `세션 종료: ${state.sessionName}${closed ? ` (${closed} panes closed)` : ""}`,
28
+ );
22
29
  } else if (sessionExists(state.sessionName)) {
23
30
  killSession(state.sessionName);
24
31
  ok(`세션 종료: ${state.sessionName}`);
@@ -1,8 +1,11 @@
1
1
  import { DIM, RESET, WHITE } from "../../shared.mjs";
2
+ import { ok } from "../render.mjs";
2
3
  import { isTeamAlive } from "../services/runtime-mode.mjs";
3
4
  import { loadTeamState, saveTeamState } from "../services/state-store.mjs";
4
- import { normalizeTaskStatus, updateTaskStatus } from "../services/task-model.mjs";
5
- import { ok } from "../render.mjs";
5
+ import {
6
+ normalizeTaskStatus,
7
+ updateTaskStatus,
8
+ } from "../services/task-model.mjs";
6
9
 
7
10
  export function teamTaskUpdate(args = []) {
8
11
  const state = loadTeamState();
@@ -14,7 +17,9 @@ export function teamTaskUpdate(args = []) {
14
17
  const nextStatus = normalizeTaskStatus(args[0]);
15
18
  const taskId = String(args[1] || "").toUpperCase();
16
19
  if (!nextStatus || !taskId) {
17
- console.log(`\n 사용법: ${WHITE}tfx multi task <pending|progress|done> <T1>${RESET}\n`);
20
+ console.log(
21
+ `\n 사용법: ${WHITE}tfx multi task <pending|progress|done> <T1>${RESET}\n`,
22
+ );
18
23
  return;
19
24
  }
20
25
 
@@ -1,13 +1,13 @@
1
- import { DIM, RESET } from "../../shared.mjs";
2
- import { isTeamAlive } from "../services/runtime-mode.mjs";
3
- import { loadTeamState } from "../services/state-store.mjs";
4
- import { renderTasks } from "../render.mjs";
5
-
6
- export function teamTasks() {
7
- const state = loadTeamState();
8
- if (!state || !isTeamAlive(state)) {
9
- console.log(`\n ${DIM}활성 팀 세션 없음${RESET}\n`);
10
- return;
11
- }
12
- renderTasks(state.tasks || []);
13
- }
1
+ import { DIM, RESET } from "../../shared.mjs";
2
+ import { renderTasks } from "../render.mjs";
3
+ import { isTeamAlive } from "../services/runtime-mode.mjs";
4
+ import { loadTeamState } from "../services/state-store.mjs";
5
+
6
+ export function teamTasks() {
7
+ const state = loadTeamState();
8
+ if (!state || !isTeamAlive(state)) {
9
+ console.log(`\n ${DIM}활성 팀 세션 없음${RESET}\n`);
10
+ return;
11
+ }
12
+ renderTasks(state.tasks || []);
13
+ }
@@ -6,7 +6,7 @@ export function renderTeamHelp() {
6
6
 
7
7
  ${BOLD}시작${RESET}
8
8
  ${WHITE}tfx multi "작업 설명"${RESET}
9
- ${WHITE}tfx multi --agents codex,gemini --lead claude "작업"${RESET}
9
+ ${WHITE}tfx multi --agents codex,antigravity --lead claude "작업"${RESET}
10
10
  ${WHITE}tfx multi --teammate-mode tmux "작업"${RESET}
11
11
  ${WHITE}tfx multi --teammate-mode wt "작업"${RESET} ${DIM}(Windows Terminal split-pane)${RESET}
12
12
  ${WHITE}tfx multi --layout 1xN "작업"${RESET} ${DIM}(세로 분할 컬럼)${RESET}
@@ -1,5 +1,3 @@
1
- import { renderTeamHelp } from "./help.mjs";
2
- import { resolveTeamCommand } from "./manifest.mjs";
3
1
  import { teamAttach } from "./commands/attach.mjs";
4
2
  import { teamControl } from "./commands/control.mjs";
5
3
  import { teamDebug } from "./commands/debug.mjs";
@@ -13,6 +11,8 @@ import { teamStatus } from "./commands/status.mjs";
13
11
  import { teamStop } from "./commands/stop.mjs";
14
12
  import { teamTaskUpdate } from "./commands/task.mjs";
15
13
  import { teamTasks } from "./commands/tasks.mjs";
14
+ import { renderTeamHelp } from "./help.mjs";
15
+ import { resolveTeamCommand } from "./manifest.mjs";
16
16
 
17
17
  const handlers = {
18
18
  attach: teamAttach,
@@ -1,18 +1,46 @@
1
1
  export const TEAM_COMMANDS = [
2
2
  { name: "status", usage: "tfx multi status", desc: "현재 팀 상태" },
3
- { name: "debug", usage: "tfx multi debug [--lines 30]", desc: "강화 디버그 출력" },
3
+ {
4
+ name: "debug",
5
+ usage: "tfx multi debug [--lines 30]",
6
+ desc: "강화 디버그 출력",
7
+ },
4
8
  { name: "tasks", usage: "tfx multi tasks", desc: "공유 태스크 목록" },
5
- { name: "task", usage: "tfx multi task <pending|progress|done> <T1>", desc: "태스크 상태 갱신" },
9
+ {
10
+ name: "task",
11
+ usage: "tfx multi task <pending|progress|done> <T1>",
12
+ desc: "태스크 상태 갱신",
13
+ },
6
14
  { name: "attach", usage: "tfx multi attach [--wt]", desc: "세션 재연결" },
7
- { name: "focus", usage: "tfx multi focus <lead|이름|번호> [--wt]", desc: "특정 팀메이트 포커스" },
8
- { name: "send", usage: "tfx multi send <lead|이름|번호> \"msg\"", desc: "팀메이트에 메시지 주입" },
9
- { name: "interrupt", usage: "tfx multi interrupt <대상>", desc: "팀메이트 인터럽트(C-c)" },
10
- { name: "control", usage: "tfx multi control <대상> <cmd> [사유]", desc: "리드 제어명령 전송" },
15
+ {
16
+ name: "focus",
17
+ usage: "tfx multi focus <lead|이름|번호> [--wt]",
18
+ desc: "특정 팀메이트 포커스",
19
+ },
20
+ {
21
+ name: "send",
22
+ usage: 'tfx multi send <lead|이름|번호> "msg"',
23
+ desc: "팀메이트에 메시지 주입",
24
+ },
25
+ {
26
+ name: "interrupt",
27
+ usage: "tfx multi interrupt <대상>",
28
+ desc: "팀메이트 인터럽트(C-c)",
29
+ },
30
+ {
31
+ name: "control",
32
+ usage: "tfx multi control <대상> <cmd> [사유]",
33
+ desc: "리드 제어명령 전송",
34
+ },
11
35
  { name: "stop", usage: "tfx multi stop", desc: "graceful 종료" },
12
36
  { name: "kill", usage: "tfx multi kill", desc: "모든 팀 세션 강제 종료" },
13
37
  { name: "list", usage: "tfx multi list", desc: "활성 세션 목록" },
14
38
  { name: "help", usage: "tfx multi help", desc: "도움말" },
15
- { name: "start", usage: "tfx multi start [options]", desc: "새 팀 세션 시작 (기본 커맨드)" },
39
+ {
40
+ name: "start",
41
+ usage: "tfx multi start [options]",
42
+ desc: "새 팀 세션 시작 (기본 커맨드)",
43
+ },
16
44
  ];
17
45
 
18
46
  export const TEAM_COMMAND_ALIASES = new Map([
@@ -25,5 +53,7 @@ export const TEAM_SUBCOMMANDS = new Set(TEAM_COMMANDS.map(({ name }) => name));
25
53
  export function resolveTeamCommand(raw) {
26
54
  if (typeof raw !== "string") return null;
27
55
  const command = raw.toLowerCase();
28
- return TEAM_SUBCOMMANDS.has(command) ? command : (TEAM_COMMAND_ALIASES.get(command) || null);
56
+ return TEAM_SUBCOMMANDS.has(command)
57
+ ? command
58
+ : TEAM_COMMAND_ALIASES.get(command) || null;
29
59
  }
@@ -1,30 +1,52 @@
1
- import { AMBER, BOLD, DIM, GRAY, GREEN, RED, RESET, WHITE, YELLOW } from "../shared.mjs";
2
-
3
- export function ok(msg) { console.log(` ${GREEN}✓${RESET} ${msg}`); }
4
- export function warn(msg) { console.log(` ${YELLOW}⚠${RESET} ${msg}`); }
5
- export function fail(msg) { console.log(` ${RED}✗${RESET} ${msg}`); }
6
-
7
- export function renderTasks(tasks = []) {
8
- if (!tasks.length) {
9
- console.log(`\n ${DIM}태스크 없음${RESET}\n`);
10
- return;
11
- }
12
-
13
- console.log(`\n ${AMBER}${BOLD}⬡ Team Tasks${RESET}\n`);
14
- for (const task of tasks) {
15
- const dep = task.depends_on?.length ? ` ${DIM}(deps: ${task.depends_on.join(",")})${RESET}` : "";
16
- const owner = task.owner ? ` ${GRAY}[${task.owner}]${RESET}` : "";
17
- console.log(` ${WHITE}${task.id}${RESET} ${String(task.status || "").padEnd(11)} ${task.title}${owner}${dep}`);
18
- }
19
- console.log("");
20
- }
21
-
22
- export function formatCompletionSuffix(member) {
23
- if (!member?.completionStatus) return "";
24
- if (member.completionStatus === "abnormal") {
25
- return ` ${RED}[abnormal:${member.completionReason || "unknown"}]${RESET}`;
26
- }
27
- if (member.completionStatus === "normal") return ` ${GREEN}[route-ok]${RESET}`;
28
- if (member.completionStatus === "unchecked") return ` ${GRAY}[route-unchecked]${RESET}`;
29
- return "";
30
- }
1
+ import {
2
+ AMBER,
3
+ BOLD,
4
+ DIM,
5
+ GRAY,
6
+ GREEN,
7
+ RED,
8
+ RESET,
9
+ WHITE,
10
+ YELLOW,
11
+ } from "../shared.mjs";
12
+
13
+ export function ok(msg) {
14
+ console.log(` ${GREEN}✓${RESET} ${msg}`);
15
+ }
16
+ export function warn(msg) {
17
+ console.log(` ${YELLOW}⚠${RESET} ${msg}`);
18
+ }
19
+ export function fail(msg) {
20
+ console.log(` ${RED}✗${RESET} ${msg}`);
21
+ }
22
+
23
+ export function renderTasks(tasks = []) {
24
+ if (!tasks.length) {
25
+ console.log(`\n ${DIM}태스크 없음${RESET}\n`);
26
+ return;
27
+ }
28
+
29
+ console.log(`\n ${AMBER}${BOLD}⬡ Team Tasks${RESET}\n`);
30
+ for (const task of tasks) {
31
+ const dep = task.depends_on?.length
32
+ ? ` ${DIM}(deps: ${task.depends_on.join(",")})${RESET}`
33
+ : "";
34
+ const owner = task.owner ? ` ${GRAY}[${task.owner}]${RESET}` : "";
35
+ console.log(
36
+ ` ${WHITE}${task.id}${RESET} ${String(task.status || "").padEnd(11)} ${task.title}${owner}${dep}`,
37
+ );
38
+ }
39
+ console.log("");
40
+ }
41
+
42
+ export function formatCompletionSuffix(member) {
43
+ if (!member?.completionStatus) return "";
44
+ if (member.completionStatus === "abnormal") {
45
+ return ` ${RED}[abnormal:${member.completionReason || "unknown"}]${RESET}`;
46
+ }
47
+ if (member.completionStatus === "normal")
48
+ return ` ${GREEN}[route-ok]${RESET}`;
49
+ if (member.completionStatus === "unchecked")
50
+ return ` ${GRAY}[route-unchecked]${RESET}`;
51
+ return "";
52
+ }
@@ -1,54 +1,74 @@
1
- import { spawn } from "node:child_process";
2
-
3
- import {
4
- getSessionAttachedCount,
5
- hasWindowsTerminal,
6
- resolveAttachCommand,
7
- } from "../../session.mjs";
8
- import { PKG_ROOT } from "./state-store.mjs";
9
-
10
- export async function launchAttachInWindowsTerminal(sessionName) {
11
- if (!hasWindowsTerminal()) return false;
12
-
13
- let attachSpec;
14
- try {
15
- attachSpec = resolveAttachCommand(sessionName);
16
- } catch {
17
- return false;
18
- }
19
-
20
- const beforeAttached = getSessionAttachedCount(sessionName);
21
- try {
22
- const child = spawn("wt", ["-w", "0", "split-pane", "-V", "-d", PKG_ROOT, attachSpec.command, ...attachSpec.args], {
23
- detached: true,
24
- stdio: "ignore",
25
- windowsHide: false,
26
- });
27
- child.unref();
28
-
29
- if (beforeAttached == null) return true;
30
- const deadline = Date.now() + 3500;
31
- while (Date.now() < deadline) {
32
- await new Promise((resolve) => setTimeout(resolve, 120));
33
- const nowAttached = getSessionAttachedCount(sessionName);
34
- if (typeof nowAttached === "number" && nowAttached > beforeAttached) return true;
35
- }
36
- } catch {}
37
- return false;
38
- }
39
-
40
- export function buildManualAttachCommand(sessionName) {
41
- try {
42
- const spec = resolveAttachCommand(sessionName);
43
- return [spec.command, ...spec.args].map((value) => {
44
- const text = String(value);
45
- return /\s/.test(text) ? `"${text.replace(/"/g, '\\"')}"` : text;
46
- }).join(" ");
47
- } catch {
48
- return `tmux attach-session -t ${sessionName}`;
49
- }
50
- }
51
-
52
- export function wantsWtAttachFallback(args = [], env = process.env) {
53
- return args.includes("--wt") || args.includes("--spawn-wt") || env.TFX_ATTACH_WT_AUTO === "1";
54
- }
1
+ import { spawn } from "node:child_process";
2
+
3
+ import {
4
+ getSessionAttachedCount,
5
+ hasWindowsTerminal,
6
+ resolveAttachCommand,
7
+ } from "../../session.mjs";
8
+ import { PKG_ROOT } from "./state-store.mjs";
9
+
10
+ export async function launchAttachInWindowsTerminal(sessionName) {
11
+ if (!hasWindowsTerminal()) return false;
12
+
13
+ let attachSpec;
14
+ try {
15
+ attachSpec = resolveAttachCommand(sessionName);
16
+ } catch {
17
+ return false;
18
+ }
19
+
20
+ const beforeAttached = getSessionAttachedCount(sessionName);
21
+ try {
22
+ const child = spawn(
23
+ "wt",
24
+ [
25
+ "-w",
26
+ "0",
27
+ "split-pane",
28
+ "-V",
29
+ "-d",
30
+ PKG_ROOT,
31
+ attachSpec.command,
32
+ ...attachSpec.args,
33
+ ],
34
+ {
35
+ detached: true,
36
+ stdio: "ignore",
37
+ windowsHide: false,
38
+ },
39
+ );
40
+ child.unref();
41
+
42
+ if (beforeAttached == null) return true;
43
+ const deadline = Date.now() + 3500;
44
+ while (Date.now() < deadline) {
45
+ await new Promise((resolve) => setTimeout(resolve, 120));
46
+ const nowAttached = getSessionAttachedCount(sessionName);
47
+ if (typeof nowAttached === "number" && nowAttached > beforeAttached)
48
+ return true;
49
+ }
50
+ } catch {}
51
+ return false;
52
+ }
53
+
54
+ export function buildManualAttachCommand(sessionName) {
55
+ try {
56
+ const spec = resolveAttachCommand(sessionName);
57
+ return [spec.command, ...spec.args]
58
+ .map((value) => {
59
+ const text = String(value);
60
+ return /\s/.test(text) ? `"${text.replace(/"/g, '\\"')}"` : text;
61
+ })
62
+ .join(" ");
63
+ } catch {
64
+ return `tmux attach-session -t ${sessionName}`;
65
+ }
66
+ }
67
+
68
+ export function wantsWtAttachFallback(args = [], env = process.env) {
69
+ return (
70
+ args.includes("--wt") ||
71
+ args.includes("--spawn-wt") ||
72
+ env.TFX_ATTACH_WT_AUTO === "1"
73
+ );
74
+ }