@triflux/remote 10.50.2 → 10.52.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 (134) hide show
  1. package/cto/brief.mjs +0 -20
  2. package/cto/collect.mjs +1 -200
  3. package/cto/current.schema.json +2 -23
  4. package/cto/hygiene.mjs +2 -61
  5. package/cto/lake-root.mjs +0 -56
  6. package/cto/status.mjs +5 -187
  7. package/hub/index.mjs +0 -17
  8. package/hub/team/agent-map.json +0 -1
  9. package/hub/team/agents-row-attach.py +120 -0
  10. package/hub/team/agents-row.mjs +200 -0
  11. package/hub/team/ansi.mjs +1 -35
  12. package/hub/team/backend.mjs +9 -145
  13. package/hub/team/claude-daemon-control.mjs +1 -2
  14. package/hub/team/claude-session-projection.mjs +5 -73
  15. package/hub/team/cli/commands/attach.mjs +1 -17
  16. package/hub/team/cli/commands/control.mjs +12 -38
  17. package/hub/team/cli/commands/debug.mjs +2 -39
  18. package/hub/team/cli/commands/focus.mjs +5 -31
  19. package/hub/team/cli/commands/interrupt.mjs +6 -25
  20. package/hub/team/cli/commands/kill.mjs +1 -30
  21. package/hub/team/cli/commands/list.mjs +0 -16
  22. package/hub/team/cli/commands/send.mjs +6 -26
  23. package/hub/team/cli/commands/start/index.mjs +42 -133
  24. package/hub/team/cli/commands/start/parse-args.mjs +15 -40
  25. package/hub/team/cli/commands/start/start-headless.mjs +138 -105
  26. package/hub/team/cli/commands/start/start-mux.mjs +2 -7
  27. package/hub/team/cli/commands/status.mjs +2 -57
  28. package/hub/team/cli/commands/stop.mjs +14 -19
  29. package/hub/team/cli/help.mjs +4 -5
  30. package/hub/team/cli/manifest.mjs +5 -1
  31. package/hub/team/cli/render.mjs +0 -12
  32. package/hub/team/cli/services/runtime-mode.mjs +27 -98
  33. package/hub/team/cli/services/state-store.mjs +5 -20
  34. package/hub/team/handoff.mjs +3 -34
  35. package/hub/team/headless.mjs +108 -1296
  36. package/hub/team/orchestrator.mjs +4 -30
  37. package/hub/team/pane.mjs +6 -28
  38. package/hub/team/psmux.mjs +3 -34
  39. package/hub/team/remote-session.mjs +1 -2
  40. package/hub/team/session-context.mjs +1 -1
  41. package/hub/team/session.mjs +7 -91
  42. package/hub/team/tui-core.mjs +1 -1
  43. package/hub/team/tui-viewer.mjs +11 -58
  44. package/hub/team/tui.mjs +8 -306
  45. package/hub/team/uds-orchestrator.mjs +2 -580
  46. package/hub/team/wt-manager.mjs +2 -2
  47. package/hub/workers/factory.mjs +6 -84
  48. package/hub/workers/interface.mjs +1 -7
  49. package/hub/workers/lib/jsonrpc-core.mjs +11 -0
  50. package/hub/workers/lib/jsonrpc-ws-uds.mjs +5 -8
  51. package/package.json +3 -8
  52. package/scripts/lib/agent-route-policy.mjs +143 -62
  53. package/scripts/lib/cli-claude.mjs +0 -14
  54. package/scripts/lib/env-probe.mjs +2 -131
  55. package/scripts/lib/gemini-profiles.mjs +2 -69
  56. package/scripts/lib/legacy-mcp-cleanup.mjs +283 -0
  57. package/scripts/lib/machine-profile.mjs +3 -17
  58. package/scripts/lib/mcp-filter.mjs +109 -53
  59. package/scripts/lib/mcp-guard-engine.mjs +40 -98
  60. package/scripts/lib/mcp-health.mjs +2 -2
  61. package/scripts/lib/mcp-server-catalog.mjs +2 -2
  62. package/scripts/lib/psmux-info.mjs +11 -59
  63. package/scripts/lib/skill-template.mjs +0 -245
  64. package/hub/hub-lifecycle.mjs +0 -248
  65. package/hub/pipe.mjs +0 -825
  66. package/hub/promote-penalties.mjs +0 -138
  67. package/hub/public/dashboard.html +0 -416
  68. package/hub/schema.sql +0 -209
  69. package/hub/server.mjs +0 -3048
  70. package/hub/store-adapter.mjs +0 -182
  71. package/hub/store.mjs +0 -1736
  72. package/hub/team/build-worker-prompt.mjs +0 -92
  73. package/hub/team/check-mcp-hub.mjs +0 -49
  74. package/hub/team/claude-native-bridge.mjs +0 -987
  75. package/hub/team/cli/commands/start/start-in-process.mjs +0 -50
  76. package/hub/team/cli/commands/start/start-wt.mjs +0 -86
  77. package/hub/team/cli/services/hub-client.mjs +0 -264
  78. package/hub/team/cli/services/native-control.mjs +0 -145
  79. package/hub/team/codex-review.mjs +0 -520
  80. package/hub/team/conductor-mesh-bridge.mjs +0 -122
  81. package/hub/team/conductor-registry.mjs +0 -121
  82. package/hub/team/conductor.mjs +0 -1529
  83. package/hub/team/consensus-meta.mjs +0 -176
  84. package/hub/team/cto-auto-collect.mjs +0 -115
  85. package/hub/team/cto-pull.mjs +0 -31
  86. package/hub/team/daemon-pty-tmux-bridge.mjs +0 -146
  87. package/hub/team/dashboard.mjs +0 -322
  88. package/hub/team/event-log.mjs +0 -84
  89. package/hub/team/execution-mode.mjs +0 -324
  90. package/hub/team/extract-completion-payload.mjs +0 -110
  91. package/hub/team/headless-bridge-session.mjs +0 -17
  92. package/hub/team/health-probe.mjs +0 -435
  93. package/hub/team/interactive-native-launcher.mjs +0 -119
  94. package/hub/team/interactive-native-worker.mjs +0 -80
  95. package/hub/team/interactive-tui-transport.mjs +0 -479
  96. package/hub/team/launcher-template.mjs +0 -144
  97. package/hub/team/lead-control.mjs +0 -191
  98. package/hub/team/mcp-selector.mjs +0 -145
  99. package/hub/team/native-supervisor.mjs +0 -460
  100. package/hub/team/native.mjs +0 -727
  101. package/hub/team/recovery-store.mjs +0 -149
  102. package/hub/team/remote-probe.mjs +0 -292
  103. package/hub/team/remote-watcher.mjs +0 -506
  104. package/hub/team/runtime-strategy.mjs +0 -200
  105. package/hub/team/schemas/worker-completion.json +0 -39
  106. package/hub/team/sentinel-capture.mjs +0 -127
  107. package/hub/team/session-sync.mjs +0 -316
  108. package/hub/team/swarm-cli.mjs +0 -390
  109. package/hub/team/swarm-hypervisor.mjs +0 -2206
  110. package/hub/team/swarm-intent.mjs +0 -216
  111. package/hub/team/swarm-locks.mjs +0 -335
  112. package/hub/team/swarm-planner.mjs +0 -371
  113. package/hub/team/swarm-preflight.mjs +0 -366
  114. package/hub/team/swarm-reconciler.mjs +0 -242
  115. package/hub/team/synapse-cli.mjs +0 -458
  116. package/hub/team/synapse-http.mjs +0 -311
  117. package/hub/team/synapse-registry.mjs +0 -618
  118. package/hub/team/tui-remote-adapter.mjs +0 -377
  119. package/hub/team/tui-synapse.mjs +0 -223
  120. package/hub/team/worker-completion-validator.mjs +0 -79
  121. package/hub/team/worker-sandbox.mjs +0 -125
  122. package/hub/team/worktree-lifecycle.mjs +0 -639
  123. package/hub/team/wt-templates.json +0 -43
  124. package/hub/tools.mjs +0 -900
  125. package/hub/workers/antigravity-route-worker.mjs +0 -179
  126. package/hub/workers/codex-app-server-worker.mjs +0 -1067
  127. package/hub/workers/delegator-mcp.mjs +0 -1098
  128. package/hub/workers/lib/jsonrpc-stdio.mjs +0 -204
  129. package/scripts/lib/claudemd-scanner.mjs +0 -223
  130. package/scripts/lib/context.mjs +0 -67
  131. package/scripts/lib/dynamic-route-cli.mjs +0 -107
  132. package/scripts/lib/handoff.mjs +0 -187
  133. package/scripts/lib/logger.mjs +0 -122
  134. package/scripts/lib/stealth-fetch.mjs +0 -176
@@ -1,29 +1,14 @@
1
1
  import { decomposeTask } from "../../../orchestrator.mjs";
2
- import {
3
- AMBER,
4
- BOLD,
5
- DIM,
6
- GREEN,
7
- RED,
8
- RESET,
9
- WHITE,
10
- } from "../../../shared.mjs";
11
- import { fail, ok, warn } from "../../render.mjs";
12
- import {
13
- getDefaultHubUrl,
14
- getHubInfo,
15
- startHubDaemon,
16
- } from "../../services/hub-client.mjs";
2
+ import { AMBER, BOLD, DIM, RESET, WHITE } from "../../../shared.mjs";
3
+ import { fail } from "../../render.mjs";
17
4
  import {
18
5
  ensureTmuxOrExit,
19
- resolveEffectiveMode,
6
+ normalizeTeammateMode,
20
7
  } from "../../services/runtime-mode.mjs";
21
8
  import { saveTeamState } from "../../services/state-store.mjs";
22
9
  import { parseTeamArgs } from "./parse-args.mjs";
23
10
  import { startHeadlessTeam } from "./start-headless.mjs";
24
- import { startInProcessTeam } from "./start-in-process.mjs";
25
11
  import { startMuxTeam } from "./start-mux.mjs";
26
- import { startWtTeam } from "./start-wt.mjs";
27
12
 
28
13
  function printStartUsage() {
29
14
  console.log(`\n ${AMBER}${BOLD}⬡ tfx multi${RESET}\n`);
@@ -43,12 +28,6 @@ function printStartUsage() {
43
28
  console.log(
44
29
  ` ${WHITE}tfx multi --dashboard-anchor window "작업"${RESET} ${DIM}(dashboard anchor: window|tab, 기본 window)${RESET}`,
45
30
  );
46
- console.log(
47
- ` ${WHITE}tfx multi --teammate-mode wt "작업"${RESET} ${DIM}(Windows Terminal split-pane)${RESET}`,
48
- );
49
- console.log(
50
- ` ${WHITE}tfx multi --teammate-mode in-process "작업"${RESET} ${DIM}(mux 불필요)${RESET}\n`,
51
- );
52
31
  }
53
32
 
54
33
  function printWorkerPreview(agents, subtasks) {
@@ -62,14 +41,6 @@ function printWorkerPreview(agents, subtasks) {
62
41
  console.log("");
63
42
  }
64
43
 
65
- function renderTmuxInstallHelp() {
66
- console.log(`\n ${RED}${BOLD}tmux 미발견${RESET}\n`);
67
- console.log(" 현재 선택한 모드는 tmux 기반 팀세션이 필요합니다.\n");
68
- console.log(
69
- ` 설치:\n Windows: ${WHITE}winget install psmux${RESET}\n macOS: ${WHITE}brew install tmux${RESET}\n Linux: ${WHITE}apt install tmux${RESET}\n`,
70
- );
71
- }
72
-
73
44
  export { parseTeamArgs };
74
45
 
75
46
  export function resolveTeamWorkers({ agents, task, assigns = [] }) {
@@ -116,37 +87,15 @@ export async function teamStart(args = []) {
116
87
  assigns,
117
88
  });
118
89
 
119
- console.log(`\n ${AMBER}${BOLD}⬡ tfx multi${RESET}\n`);
120
-
121
- // P1b: 워커 수 계산 — 단일 워커 headless에는 Hub 불필요
122
- const workerCount = agents.length;
123
- const needsHub = workerCount >= 2 || teammateMode !== "headless";
90
+ const effectiveMode = normalizeTeammateMode(teammateMode);
91
+ // headless 워커도 tmux 방에서 돈다. 방이 없으면 claude agents 행도 열 수 없다.
92
+ ensureTmuxOrExit();
93
+ const effectiveNativeBridge =
94
+ effectiveMode === "headless" && !nativeBridgeUiOptOut ? true : nativeBridge;
124
95
 
125
- let hub = null;
126
- if (needsHub) {
127
- hub = await getHubInfo();
128
- if (!hub) {
129
- process.stdout.write(" Hub 시작 중...");
130
- try {
131
- hub = await startHubDaemon();
132
- } catch (error) {
133
- if (error?.code === "HUB_SERVER_MISSING")
134
- fail("hub/server.mjs 없음 — hub 모듈이 설치되지 않음");
135
- }
136
- console.log(` ${hub ? `${GREEN}✓${RESET}` : `${RED}✗${RESET}`}`);
137
- if (!hub) warn("Hub 시작 실패 — 수동으로 실행: tfx hub start");
138
- } else ok(`Hub: ${DIM}${hub.url}${RESET}`);
139
- } else {
140
- ok(`Hub: ${DIM}건너뜀 (단일 워커 headless)${RESET}`);
141
- }
96
+ console.log(`\n ${AMBER}${BOLD}⬡ tfx multi${RESET}\n`);
142
97
 
143
98
  const sessionId = `tfx-multi-${Date.now().toString(36).slice(-4)}${Math.random().toString(36).slice(2, 6)}`;
144
- const hubUrl = hub?.url || getDefaultHubUrl();
145
- const { mode: effectiveMode, warnings: modeWarnings } =
146
- resolveEffectiveMode(teammateMode);
147
- for (const message of modeWarnings) warn(message);
148
- const effectiveNativeBridge =
149
- effectiveMode === "headless" && !nativeBridgeUiOptOut ? true : nativeBridge;
150
99
 
151
100
  console.log(` 세션: ${WHITE}${sessionId}${RESET}`);
152
101
  console.log(` 모드: ${effectiveMode}`);
@@ -156,83 +105,43 @@ export async function teamStart(args = []) {
156
105
  );
157
106
  printWorkerPreview(agents, subtasks);
158
107
 
159
- if (effectiveMode === "tmux") {
160
- try {
161
- ensureTmuxOrExit();
162
- } catch {
163
- return renderTmuxInstallHelp();
164
- }
108
+ if (effectiveMode === "headless") {
109
+ return startHeadlessTeam({
110
+ sessionId,
111
+ task,
112
+ lead,
113
+ agents,
114
+ subtasks,
115
+ layout,
116
+ assigns,
117
+ autoAttach,
118
+ progressive,
119
+ timeoutSec,
120
+ verbose,
121
+ dashboard,
122
+ dashboardLayout,
123
+ dashboardSize,
124
+ dashboardAnchor,
125
+ mcpProfile,
126
+ model,
127
+ cwd,
128
+ nativeBridge: effectiveNativeBridge,
129
+ nativeBridgeMode,
130
+ });
165
131
  }
166
132
 
167
- const state =
168
- effectiveMode === "in-process"
169
- ? await startInProcessTeam({
170
- sessionId,
171
- task,
172
- lead,
173
- agents,
174
- subtasks,
175
- hubUrl,
176
- })
177
- : effectiveMode === "headless"
178
- ? await startHeadlessTeam({
179
- sessionId,
180
- task,
181
- lead,
182
- agents,
183
- subtasks,
184
- layout,
185
- assigns,
186
- autoAttach,
187
- progressive,
188
- timeoutSec,
189
- verbose,
190
- dashboard,
191
- dashboardLayout,
192
- dashboardSize,
193
- dashboardAnchor,
194
- mcpProfile,
195
- model,
196
- cwd,
197
- nativeBridge: effectiveNativeBridge,
198
- nativeBridgeMode,
199
- })
200
- : effectiveMode === "wt"
201
- ? await startWtTeam({
202
- sessionId,
203
- task,
204
- lead,
205
- agents,
206
- subtasks,
207
- layout,
208
- hubUrl,
209
- })
210
- : await startMuxTeam({
211
- sessionId,
212
- task,
213
- lead,
214
- agents,
215
- subtasks,
216
- layout,
217
- hubUrl,
218
- teammateMode: effectiveMode,
219
- });
133
+ const state = await startMuxTeam({
134
+ sessionId,
135
+ task,
136
+ lead,
137
+ agents,
138
+ subtasks,
139
+ layout,
140
+ teammateMode: effectiveMode,
141
+ });
220
142
 
221
- if (!state) return fail("in-process supervisor 시작 실패");
143
+ if (!state) return fail("팀 세션 시작 실패");
222
144
  state.sessionId = sessionId;
223
145
  saveTeamState(state, sessionId);
224
146
  if (typeof state.postSave === "function") state.postSave();
225
- if (effectiveMode === "in-process") {
226
- ok("네이티브 in-process 팀 시작 완료");
227
- console.log(` ${DIM}tmux 없이 실행됨 (직접 CLI 프로세스)${RESET}`);
228
- console.log(` ${DIM}제어: tfx multi send/control/tasks/status${RESET}\n`);
229
- } else if (effectiveMode === "wt") {
230
- ok("Windows Terminal wt 팀 시작 완료");
231
- console.log(
232
- ` ${DIM}현재 pane 기준으로 ${state.layout} 분할 생성됨${RESET}`,
233
- );
234
- console.log(
235
- ` ${DIM}wt 모드는 자동 프롬프트 주입/Hub direct 제어(send/control)가 제한됩니다.${RESET}\n`,
236
- );
237
- }
238
147
  }
@@ -1,3 +1,4 @@
1
+ import { createRequire } from "node:module";
1
2
  import { resolve } from "node:path";
2
3
  import { parseDashboardAnchor } from "../../../dashboard-anchor.mjs";
3
4
  import { parseDashboardLayout } from "../../../dashboard-layout.mjs";
@@ -6,35 +7,7 @@ import {
6
7
  normalizeTeammateMode,
7
8
  } from "../../services/runtime-mode.mjs";
8
9
 
9
- // --assign 파싱 시 마지막 콜론 뒤를 role로 인식할 알려진 역할/CLI 이름
10
- const KNOWN_ROLES = new Set([
11
- "codex",
12
- "antigravity",
13
- "agy",
14
- "gemini",
15
- "claude",
16
- "executor",
17
- "architect",
18
- "planner",
19
- "analyst",
20
- "critic",
21
- "debugger",
22
- "verifier",
23
- "code-reviewer",
24
- "security-reviewer",
25
- "test-engineer",
26
- "designer",
27
- "writer",
28
- "researcher",
29
- "scientist",
30
- ]);
31
-
32
- const NATIVE_BRIDGE_MODES = new Set([
33
- "roster",
34
- "agents",
35
- "interactive-attach",
36
- "claude-wrapper",
37
- ]);
10
+ const AGENT_TO_CLI = createRequire(import.meta.url)("../../../agent-map.json");
38
11
 
39
12
  /**
40
13
  * --assign "cli:prompt:role" 형식을 콜론-안전하게 파싱한다.
@@ -42,7 +15,7 @@ const NATIVE_BRIDGE_MODES = new Set([
42
15
  *
43
16
  * 규칙:
44
17
  * 1. 첫 번째 콜론 앞 = CLI 이름
45
- * 2. 마지막 콜론 뒤가 KNOWN_ROLES에 있으면 role, 나머지가 prompt
18
+ * 2. 마지막 콜론 뒤가 agent-map.json에 있으면 role, 나머지가 prompt
46
19
  * 3. 그 외에는 첫 콜론 뒤 전체가 prompt, role은 빈 문자열
47
20
  */
48
21
  function parseAssignValue(raw) {
@@ -58,7 +31,7 @@ function parseAssignValue(raw) {
58
31
  .slice(lastColon + 1)
59
32
  .trim()
60
33
  .toLowerCase();
61
- if (KNOWN_ROLES.has(candidate)) {
34
+ if (Object.hasOwn(AGENT_TO_CLI, candidate)) {
62
35
  return { cli, prompt: rest.slice(0, lastColon).trim(), role: candidate };
63
36
  }
64
37
  }
@@ -72,7 +45,7 @@ export function parseTeamArgs(args = []) {
72
45
  let layout = "2x2";
73
46
  let teammateMode = "auto";
74
47
  const taskParts = [];
75
- const assigns = []; // --assign "codex:프롬프트:역할" 형식
48
+ const assigns = [];
76
49
  let autoAttach = true;
77
50
  let progressive = true;
78
51
  let timeoutSec = 0;
@@ -109,14 +82,10 @@ export function parseTeamArgs(args = []) {
109
82
  } else if (current === "--assign" && args[index + 1]) {
110
83
  const parsed = parseAssignValue(args[++index]);
111
84
  if (parsed) assigns.push(parsed);
112
- } else if (current === "--auto-attach") {
113
- autoAttach = true;
114
85
  } else if (current === "--no-auto-attach") {
115
86
  autoAttach = false;
116
87
  } else if (current === "--verbose") {
117
88
  verbose = true;
118
- } else if (current === "--dashboard") {
119
- dashboard = true;
120
89
  } else if (current === "--no-dashboard") {
121
90
  dashboard = false;
122
91
  } else if (current === "--dashboard-layout" && args[index + 1]) {
@@ -159,9 +128,9 @@ export function parseTeamArgs(args = []) {
159
128
  nativeBridgeUiOptOut = true;
160
129
  } else if (current === "--native-bridge-mode") {
161
130
  const mode = args[++index];
162
- if (!NATIVE_BRIDGE_MODES.has(mode)) {
131
+ if (mode !== "agents") {
163
132
  throw new Error(
164
- `unknown native bridge mode: ${mode || ""}; expected roster, agents, interactive-attach, or claude-wrapper`,
133
+ `unknown native bridge mode: ${mode || ""}; expected agents`,
165
134
  );
166
135
  }
167
136
  nativeBridge = true;
@@ -174,6 +143,8 @@ export function parseTeamArgs(args = []) {
174
143
  p = p[1].toUpperCase() + ":" + p.slice(2);
175
144
  }
176
145
  cwd = resolve(p);
146
+ } else if (current === "--auto-attach" || current === "--dashboard") {
147
+ // 이전 설치 스킬이 넘기는 기본값 플래그는 무시한다.
177
148
  } else if (current.startsWith("-")) {
178
149
  console.warn(` ⚠ 미인식 플래그 무시: ${current}`);
179
150
  } else {
@@ -181,7 +152,11 @@ export function parseTeamArgs(args = []) {
181
152
  }
182
153
  }
183
154
 
184
- const normalizedTeammateMode = normalizeTeammateMode(teammateMode);
155
+ const task = taskParts.join(" ").trim();
156
+ const normalizedTeammateMode =
157
+ task || assigns.length > 0
158
+ ? normalizeTeammateMode(teammateMode)
159
+ : teammateMode;
185
160
  if (
186
161
  !nativeBridgeExplicit &&
187
162
  !nativeBridgeUiOptOut &&
@@ -195,7 +170,7 @@ export function parseTeamArgs(args = []) {
195
170
  lead,
196
171
  layout: normalizeLayout(layout),
197
172
  teammateMode: normalizedTeammateMode,
198
- task: taskParts.join(" ").trim(),
173
+ task,
199
174
  assigns,
200
175
  autoAttach,
201
176
  progressive,
@@ -6,7 +6,7 @@ import {
6
6
  } from "../../../headless.mjs";
7
7
  import { AMBER, DIM, GREEN, RESET } from "../../../shared.mjs";
8
8
  import { ok } from "../../render.mjs";
9
- import { clearTeamState } from "../../services/state-store.mjs";
9
+ import { clearTeamState, saveTeamState } from "../../services/state-store.mjs";
10
10
  import { buildTasks } from "../../services/task-model.mjs";
11
11
 
12
12
  export function buildHeadlessAssignments({
@@ -66,6 +66,7 @@ export async function startHeadlessTeam({
66
66
  cwd,
67
67
  nativeBridge,
68
68
  nativeBridgeMode,
69
+ _deps = {},
69
70
  }) {
70
71
  const assignments = buildHeadlessAssignments({
71
72
  agents,
@@ -76,30 +77,59 @@ export async function startHeadlessTeam({
76
77
  cwd,
77
78
  });
78
79
 
79
- const _startedAt = Date.now();
80
+ const state = {
81
+ sessionId,
82
+ sessionName: sessionId,
83
+ task,
84
+ lead,
85
+ agents: assignments.map((assignment) => assignment.cli),
86
+ layout,
87
+ teammateMode: "headless",
88
+ ownerPid: process.pid,
89
+ startedAt: Date.now(),
90
+ members: assignments.map((assignment, index) => ({
91
+ role: "worker",
92
+ name: `worker-${index + 1}`,
93
+ cli: assignment.cli,
94
+ pane: "",
95
+ subtask: assignment.prompt,
96
+ })),
97
+ };
98
+ state.tasks = buildTasks(
99
+ assignments.map((assignment) => assignment.prompt),
100
+ state.members,
101
+ );
102
+ saveTeamState(state, sessionId);
80
103
  ok(`headless ${assignments.length}워커 시작`);
81
104
  if (nativeBridge) {
82
105
  process.stderr.write(
83
- `[headless] Claude native bridge mode: ${nativeBridgeMode || "roster"}\n`,
106
+ `[headless] Claude native bridge mode: ${nativeBridgeMode || "agents"}\n`,
84
107
  );
85
108
  }
86
109
 
87
- const handle = await runHeadlessInteractive(sessionId, assignments, {
88
- timeoutSec: timeoutSec || 900,
89
- layout,
90
- autoAttach: !!autoAttach,
91
- leadPane: process.env.TMUX_PANE,
92
- leadTmux: process.env.TMUX,
93
- dashboard: !!dashboard,
94
- dashboardLayout,
95
- dashboardSize: dashboardSize ?? 0.5,
96
- dashboardAnchor,
97
- progressive: progressive !== false,
98
- nativeBridge,
99
- nativeBridgeMode,
100
- progressIntervalSec: verbose ? 10 : 0,
101
- onProgress: verbose
102
- ? function onProgress(event) {
110
+ let handle;
111
+ try {
112
+ const run = _deps.runHeadlessInteractive || runHeadlessInteractive;
113
+ handle = await run(sessionId, assignments, {
114
+ timeoutSec: timeoutSec || 900,
115
+ layout,
116
+ autoAttach: !!autoAttach,
117
+ leadPane: process.env.TMUX_PANE,
118
+ leadTmux: process.env.TMUX,
119
+ dashboard: !!dashboard,
120
+ dashboardLayout,
121
+ dashboardSize: dashboardSize ?? 0.5,
122
+ dashboardAnchor,
123
+ progressive: progressive !== false,
124
+ nativeBridge,
125
+ nativeBridgeMode,
126
+ progressIntervalSec: verbose ? 10 : 0,
127
+ onProgress(event) {
128
+ if (event.type === "session_created") {
129
+ state.sessionName = event.sessionName;
130
+ saveTeamState(state, sessionId);
131
+ }
132
+ if (verbose) {
103
133
  if (event.type === "session_created") {
104
134
  console.log(` ${DIM}세션: ${event.sessionName}${RESET}`);
105
135
  } else if (event.type === "worker_added") {
@@ -128,104 +158,107 @@ export async function startHeadlessTeam({
128
158
  );
129
159
  }
130
160
  }
131
- : undefined,
132
- });
133
-
134
- // 최소 결과 요약
135
- const results = handle.results;
136
- const succeeded = results.filter((r) => r.matched && r.exitCode === 0);
137
- const failed = results.filter((r) => !r.matched || r.exitCode !== 0);
161
+ },
162
+ });
138
163
 
139
- ok(
140
- `헤드리스 완료: ${succeeded.length}성공 / ${failed.length}실패 / ${results.length}전체`,
141
- );
142
- // 머신 파싱용 완료 마커 — background task에서 결과 회수 시점 판단에 사용
143
- console.log(
144
- `=== HEADLESS_COMPLETE succeeded=${succeeded.length} failed=${failed.length} total=${results.length} ===`,
145
- );
164
+ // 최소 결과 요약
165
+ const results = handle.results;
166
+ const succeeded = results.filter((r) => r.matched && r.exitCode === 0);
167
+ const failed = results.filter((r) => !r.matched || r.exitCode !== 0);
146
168
 
147
- if (failed.length > 0) {
148
- for (const r of failed)
149
- console.log(
150
- ` ${AMBER}✗${RESET} ${r.paneName} (${r.cli}) exit=${r.exitCode}`,
151
- );
152
- }
169
+ ok(
170
+ `헤드리스 완료: ${succeeded.length}성공 / ${failed.length}실패 / ${results.length}전체`,
171
+ );
172
+ // background task가 이 완료 마커로 결과 회수 시점을 판단한다.
173
+ console.log(
174
+ `=== HEADLESS_COMPLETE succeeded=${succeeded.length} failed=${failed.length} total=${results.length} ===`,
175
+ );
153
176
 
154
- // handoff 요약 (Lead 토큰 절약 포맷)
155
- for (const r of results) {
156
- const icon =
157
- r.matched && r.exitCode === 0 ? `${GREEN}✓${RESET}` : `${AMBER}✗${RESET}`;
158
- if (r.handoffFormatted) {
159
- const tag = r.handoffFallback ? `${DIM}(fallback)${RESET}` : "";
160
- console.log(` ${icon} ${r.paneName} ${tag}`);
161
- for (const line of r.handoffFormatted.split("\n")) {
162
- console.log(` ${DIM}${line}${RESET}`);
163
- }
164
- } else {
165
- if (r.resultFile) console.log(` ${icon} ${r.paneName}: ${r.resultFile}`);
177
+ if (failed.length > 0) {
178
+ for (const r of failed)
179
+ console.log(
180
+ ` ${AMBER}✗${RESET} ${r.paneName} (${r.cli}) exit=${r.exitCode}`,
181
+ );
166
182
  }
167
- }
168
183
 
169
- // --verbose: 기존 장황한 출력 (200자 preview)
170
- if (verbose) {
184
+ // handoff 요약 (Lead 토큰 절약 포맷)
171
185
  for (const r of results) {
172
- if (r.output) {
173
- const preview =
174
- r.output.length > 200 ? `${r.output.slice(0, 200)}…` : r.output;
175
- console.log(
176
- `\n ${DIM}── ${r.paneName} (${r.cli}${r.role ? `, ${r.role}` : ""}) ──${RESET}`,
177
- );
178
- console.log(` ${preview}`);
186
+ const icon =
187
+ r.matched && r.exitCode === 0
188
+ ? `${GREEN}✓${RESET}`
189
+ : `${AMBER}✗${RESET}`;
190
+ if (r.handoffFormatted) {
191
+ const tag = r.handoffFallback ? `${DIM}(fallback)${RESET}` : "";
192
+ console.log(` ${icon} ${r.paneName} ${tag}`);
193
+ for (const line of r.handoffFormatted.split("\n")) {
194
+ console.log(` ${DIM}${line}${RESET}`);
195
+ }
196
+ } else {
197
+ if (r.resultFile)
198
+ console.log(` ${icon} ${r.paneName}: ${r.resultFile}`);
179
199
  }
180
200
  }
181
- }
182
201
 
183
- // dashboard 모드: tui-viewer가 최종 상태를 렌더링할 시간 확보
184
- // WT pane spawn (~1s) + node 기동 (~500ms) + 첫 폴링 (~500ms) + 렌더 여유
185
- if (dashboard) await new Promise((r) => setTimeout(r, 5000));
202
+ // --verbose: 기존 장황한 출력 (200자 preview)
203
+ if (verbose) {
204
+ for (const r of results) {
205
+ if (r.output) {
206
+ const preview =
207
+ r.output.length > 200 ? `${r.output.slice(0, 200)}…` : r.output;
208
+ console.log(
209
+ `\n ${DIM}── ${r.paneName} (${r.cli}${r.role ? `, ${r.role}` : ""}) ──${RESET}`,
210
+ );
211
+ console.log(` ${preview}`);
212
+ }
213
+ }
214
+ }
186
215
 
187
- // 세션 정리
188
- handle.kill();
216
+ // dashboard 모드: tui-viewer가 최종 상태를 렌더링할 시간 확보
217
+ // WT pane spawn (~1s) + node 기동 (~500ms) + 첫 폴링 (~500ms) + 렌더 여유
218
+ if (dashboard) await new Promise((r) => setTimeout(r, 5000));
189
219
 
190
- const members = [
191
- {
192
- role: "lead",
193
- name: "lead",
194
- cli: lead,
195
- pane: `${handle.sessionName}:0.0`,
196
- },
197
- ...results.map((r, i) => ({
198
- role: "worker",
199
- name: r.paneName,
200
- cli: r.cli,
201
- pane: r.paneId || "",
202
- subtask: assignments[i]?.prompt,
203
- })),
204
- ];
220
+ const members = [
221
+ {
222
+ role: "lead",
223
+ name: "lead",
224
+ cli: lead,
225
+ pane: `${handle.sessionName}:0.0`,
226
+ },
227
+ ...results.map((r, i) => ({
228
+ role: "worker",
229
+ name: r.paneName,
230
+ cli: r.cli,
231
+ pane: r.paneId || "",
232
+ subtask: assignments[i]?.prompt,
233
+ })),
234
+ ];
205
235
 
206
- return {
207
- sessionName: handle.sessionName,
208
- task,
209
- lead,
210
- agents: assignments.map((a) => a.cli),
211
- layout,
212
- teammateMode: "headless",
213
- startedAt: Date.now(),
214
- members,
215
- headlessResults: results,
216
- handoffs: results.map((r) => ({
217
- paneName: r.paneName,
218
- cli: r.cli,
219
- ...r.handoff,
220
- })),
221
- tasks: buildTasks(
222
- assignments.map((a) => a.prompt),
223
- members.filter((m) => m.role === "worker"),
224
- ),
225
- postSave() {
226
- // headless는 실행 완료 후 즉시 정리 — HUD에 잔존 방지
236
+ return {
237
+ ...state,
238
+ sessionName: handle.sessionName,
239
+ task,
240
+ lead,
241
+ agents: assignments.map((a) => a.cli),
242
+ layout,
243
+ teammateMode: "headless",
244
+ members,
245
+ headlessResults: results,
246
+ handoffs: results.map((r) => ({
247
+ paneName: r.paneName,
248
+ cli: r.cli,
249
+ ...r.handoff,
250
+ })),
251
+ tasks: buildTasks(
252
+ assignments.map((a) => a.prompt),
253
+ members.filter((m) => m.role === "worker"),
254
+ ),
255
+ };
256
+ } finally {
257
+ try {
258
+ handle?.kill();
259
+ } finally {
227
260
  clearTeamState(sessionId);
228
261
  console.log(`\n ${DIM}세션 정리 완료.${RESET}\n`);
229
- },
230
- };
262
+ }
263
+ }
231
264
  }
@@ -10,7 +10,7 @@ import {
10
10
  import { BOLD, DIM, GREEN, RESET } from "../../../shared.mjs";
11
11
  import { ok, warn } from "../../render.mjs";
12
12
  import { toAgentId } from "../../services/member-selector.mjs";
13
- import { PKG_ROOT, TEAM_PROFILE } from "../../services/state-store.mjs";
13
+ import { PKG_ROOT } from "../../services/state-store.mjs";
14
14
  import { buildTasks } from "../../services/task-model.mjs";
15
15
 
16
16
  export async function startMuxTeam({
@@ -20,7 +20,6 @@ export async function startMuxTeam({
20
20
  agents,
21
21
  subtasks,
22
22
  layout,
23
- hubUrl,
24
23
  teammateMode,
25
24
  }) {
26
25
  const paneCount = agents.length + 1;
@@ -64,7 +63,6 @@ export async function startMuxTeam({
64
63
  ok("CLI 초기화 대기 (3초)...");
65
64
  await new Promise((resolve) => setTimeout(resolve, 3000));
66
65
  const promptInjectionFailures = await orchestrate(sessionId, assignments, {
67
- hubUrl,
68
66
  teammateMode,
69
67
  lead: { target: leadTarget, cli: lead, task },
70
68
  onInjectionFailure(failure) {
@@ -89,7 +87,6 @@ export async function startMuxTeam({
89
87
  layout: effectiveLayout,
90
88
  teammateMode,
91
89
  startedAt: Date.now(),
92
- hubUrl,
93
90
  promptInjectionFailures,
94
91
  members,
95
92
  panes: Object.fromEntries(
@@ -109,9 +106,7 @@ export async function startMuxTeam({
109
106
  members.filter((member) => member.role === "worker"),
110
107
  ),
111
108
  postSave() {
112
- const profilePrefix =
113
- TEAM_PROFILE === "team" ? "" : `TFX_TEAM_PROFILE=${TEAM_PROFILE} `;
114
- const taskListCommand = `${profilePrefix}${process.execPath} ${join(PKG_ROOT, "bin", "triflux.mjs")} team tasks`;
109
+ const taskListCommand = `${process.execPath} ${join(PKG_ROOT, "bin", "triflux.mjs")} team tasks`;
115
110
  configureTeammateKeybindings(sessionId, {
116
111
  inProcess: false,
117
112
  taskListCommand,