@triflux/remote 10.50.1 → 10.51.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 (120) hide show
  1. package/cto/brief.mjs +0 -12
  2. package/cto/collect.mjs +16 -247
  3. package/cto/current.schema.json +4 -20
  4. package/cto/events.mjs +13 -294
  5. package/cto/hygiene.mjs +26 -346
  6. package/cto/lake-root.mjs +114 -16
  7. package/cto/status.mjs +33 -128
  8. package/hub/index.mjs +0 -8
  9. package/hub/store-adapter.mjs +1 -1
  10. package/hub/team/agent-map.json +0 -1
  11. package/hub/team/ansi.mjs +1 -35
  12. package/hub/team/backend.mjs +9 -145
  13. package/hub/team/cli/commands/attach.mjs +1 -17
  14. package/hub/team/cli/commands/control.mjs +12 -38
  15. package/hub/team/cli/commands/debug.mjs +1 -37
  16. package/hub/team/cli/commands/focus.mjs +5 -31
  17. package/hub/team/cli/commands/interrupt.mjs +6 -25
  18. package/hub/team/cli/commands/kill.mjs +1 -30
  19. package/hub/team/cli/commands/list.mjs +0 -16
  20. package/hub/team/cli/commands/send.mjs +6 -26
  21. package/hub/team/cli/commands/start/index.mjs +41 -133
  22. package/hub/team/cli/commands/start/parse-args.mjs +15 -40
  23. package/hub/team/cli/commands/start/start-headless.mjs +138 -105
  24. package/hub/team/cli/commands/start/start-mux.mjs +0 -3
  25. package/hub/team/cli/commands/status.mjs +2 -57
  26. package/hub/team/cli/commands/stop.mjs +14 -19
  27. package/hub/team/cli/help.mjs +4 -5
  28. package/hub/team/cli/manifest.mjs +5 -1
  29. package/hub/team/cli/render.mjs +0 -12
  30. package/hub/team/cli/services/hub-client.mjs +2 -88
  31. package/hub/team/cli/services/runtime-mode.mjs +27 -98
  32. package/hub/team/handoff.mjs +3 -34
  33. package/hub/team/headless.mjs +14 -755
  34. package/hub/team/orchestrator.mjs +4 -30
  35. package/hub/team/pane.mjs +6 -28
  36. package/hub/team/psmux.mjs +16 -403
  37. package/hub/team/remote-session.mjs +1 -2
  38. package/hub/team/session.mjs +8 -119
  39. package/hub/team/swarm-intent.mjs +0 -198
  40. package/hub/team/terminal-opener.mjs +11 -35
  41. package/hub/team/tui-core.mjs +1 -1
  42. package/hub/team/tui-viewer.mjs +2 -2
  43. package/hub/team/tui.mjs +8 -269
  44. package/hub/team/wt-manager.mjs +2 -2
  45. package/hub/workers/factory.mjs +4 -37
  46. package/hub/workers/lib/jsonrpc-ws-uds.mjs +0 -2
  47. package/package.json +1 -1
  48. package/scripts/lib/agent-route-policy.mjs +162 -14
  49. package/scripts/lib/claudemd-scanner.mjs +2 -199
  50. package/scripts/lib/cli-claude.mjs +0 -2
  51. package/scripts/lib/context.mjs +3 -19
  52. package/scripts/lib/env-probe.mjs +5 -64
  53. package/scripts/lib/gemini-profiles.mjs +2 -69
  54. package/scripts/lib/legacy-mcp-cleanup.mjs +14 -3
  55. package/scripts/lib/machine-profile.mjs +3 -17
  56. package/scripts/lib/mcp-filter.mjs +109 -53
  57. package/scripts/lib/mcp-health.mjs +2 -2
  58. package/scripts/lib/mcp-server-catalog.mjs +2 -2
  59. package/scripts/lib/psmux-info.mjs +25 -104
  60. package/scripts/lib/skill-template.mjs +0 -271
  61. package/cto/hygiene-actions.mjs +0 -355
  62. package/cto/hygiene-notify.mjs +0 -170
  63. package/hub/team/build-worker-prompt.mjs +0 -92
  64. package/hub/team/check-mcp-hub.mjs +0 -49
  65. package/hub/team/claude-native-bridge.mjs +0 -987
  66. package/hub/team/cli/commands/start/start-in-process.mjs +0 -50
  67. package/hub/team/cli/commands/start/start-wt.mjs +0 -86
  68. package/hub/team/cli/services/native-control.mjs +0 -145
  69. package/hub/team/codex-review.mjs +0 -520
  70. package/hub/team/conductor-mesh-bridge.mjs +0 -122
  71. package/hub/team/conductor.mjs +0 -1529
  72. package/hub/team/consensus-meta.mjs +0 -176
  73. package/hub/team/cto-pull.mjs +0 -31
  74. package/hub/team/daemon-pty-tmux-bridge.mjs +0 -146
  75. package/hub/team/dashboard.mjs +0 -322
  76. package/hub/team/event-log.mjs +0 -84
  77. package/hub/team/execution-mode.mjs +0 -324
  78. package/hub/team/extract-completion-payload.mjs +0 -110
  79. package/hub/team/headless-bridge-session.mjs +0 -17
  80. package/hub/team/health-probe.mjs +0 -435
  81. package/hub/team/interactive-native-launcher.mjs +0 -119
  82. package/hub/team/interactive-native-worker.mjs +0 -80
  83. package/hub/team/interactive-tui-transport.mjs +0 -479
  84. package/hub/team/launcher-template.mjs +0 -144
  85. package/hub/team/mcp-selector.mjs +0 -145
  86. package/hub/team/native-supervisor.mjs +0 -460
  87. package/hub/team/native.mjs +0 -727
  88. package/hub/team/recovery-store.mjs +0 -149
  89. package/hub/team/remote-probe.mjs +0 -292
  90. package/hub/team/remote-watcher.mjs +0 -506
  91. package/hub/team/runtime-strategy.mjs +0 -200
  92. package/hub/team/schemas/worker-completion.json +0 -39
  93. package/hub/team/sentinel-capture.mjs +0 -127
  94. package/hub/team/swarm-cli.mjs +0 -390
  95. package/hub/team/swarm-hypervisor.mjs +0 -2206
  96. package/hub/team/swarm-locks.mjs +0 -335
  97. package/hub/team/swarm-planner.mjs +0 -371
  98. package/hub/team/swarm-preflight.mjs +0 -366
  99. package/hub/team/swarm-reconciler.mjs +0 -242
  100. package/hub/team/tui-remote-adapter.mjs +0 -377
  101. package/hub/team/worker-completion-validator.mjs +0 -79
  102. package/hub/team/worktree-lifecycle.mjs +0 -639
  103. package/hub/team/wt-templates.json +0 -43
  104. package/hub/workers/antigravity-route-worker.mjs +0 -179
  105. package/hub/workers/gemini-worker.mjs +0 -217
  106. package/scripts/lib/agent-json.mjs +0 -27
  107. package/scripts/lib/async.mjs +0 -174
  108. package/scripts/lib/cli-agy.mjs +0 -85
  109. package/scripts/lib/cli-codex.mjs +0 -62
  110. package/scripts/lib/cli-gemini.mjs +0 -37
  111. package/scripts/lib/dynamic-route-cli.mjs +0 -107
  112. package/scripts/lib/env.mjs +0 -14
  113. package/scripts/lib/hub.mjs +0 -48
  114. package/scripts/lib/pid.mjs +0 -63
  115. package/scripts/lib/quota.mjs +0 -47
  116. package/scripts/lib/skill-state.mjs +0 -220
  117. package/scripts/lib/team.mjs +0 -75
  118. package/scripts/lib/timeout.mjs +0 -66
  119. package/scripts/lib/tmp.mjs +0 -26
  120. package/scripts/lib/toml.mjs +0 -132
@@ -1,5 +1,4 @@
1
- // hub/team/remote-session.mjs — Remote session primitives for swarm integration
2
- // Extracted from scripts/remote-spawn.mjs for reuse by swarm-hypervisor.
1
+ // Remote session primitives.
3
2
  // Pure functions + SSH operations. No psmux, no WT, no CLI arg parsing.
4
3
 
5
4
  import { execFileSync } from "node:child_process";
@@ -1,5 +1,4 @@
1
- // hub/team/session.mjs — tmux/psmux/wt 세션 생명주기 관리
2
- // 의존성: child_process (Node.js 내장)만 사용
1
+ // tmux/psmux 세션 생명주기 관리
3
2
  import { execSync, spawnSync } from "node:child_process";
4
3
  import { resolveGitBashExecutable } from "@triflux/core/hub/lib/bash-path.mjs";
5
4
  import { getEnvironment } from "@triflux/core/hub/lib/env-detect.mjs";
@@ -16,7 +15,6 @@ import {
16
15
  psmuxExec,
17
16
  psmuxSessionExists,
18
17
  } from "./psmux.mjs";
19
- import { createWtManager } from "./wt-manager.mjs";
20
18
 
21
19
  /** Windows Terminal 실행 파일 존재 여부 */
22
20
  export function hasWindowsTerminal() {
@@ -55,25 +53,6 @@ function hasGitBashTmux() {
55
53
  }
56
54
  }
57
55
 
58
- function getCommandVersion(command) {
59
- const r = spawnSync(command, ["-V"], {
60
- encoding: "utf8",
61
- timeout: 3000,
62
- stdio: ["ignore", "pipe", "pipe"],
63
- windowsHide: true,
64
- });
65
- if ((r.status ?? 1) !== 0) return null;
66
- return `${r.stdout || ""}${r.stderr || ""}`.trim();
67
- }
68
-
69
- function isPsmuxVersion(command) {
70
- try {
71
- return /\bpsmux\b/i.test(getCommandVersion(command) || "");
72
- } catch {
73
- return false;
74
- }
75
- }
76
-
77
56
  function isPsmuxCommandName(command) {
78
57
  const name = String(command || "")
79
58
  .replace(/\\/g, "/")
@@ -83,10 +62,6 @@ function isPsmuxCommandName(command) {
83
62
  return name === "psmux" || name === "psmux.exe" || name === "psmux.cmd";
84
63
  }
85
64
 
86
- function hasLiteralPsmuxBinary(command = process.env.PSMUX_BIN || "psmux") {
87
- return isPsmuxVersion(command);
88
- }
89
-
90
65
  /**
91
66
  * 터미널 멀티플렉서 감지 (결과 캐싱 — 프로세스 수명 동안 불변)
92
67
  * @returns {'tmux'|'git-bash-tmux'|'psmux'|null}
@@ -105,10 +80,6 @@ export function detectMultiplexer() {
105
80
  _cachedMux = "tmux";
106
81
  return _cachedMux;
107
82
  }
108
- if (hasLiteralPsmuxBinary()) {
109
- _cachedMux = "psmux";
110
- return _cachedMux;
111
- }
112
83
  _cachedMux = null;
113
84
  return _cachedMux;
114
85
  }
@@ -150,7 +121,7 @@ function tmux(args, opts = {}) {
150
121
  return psmuxExec(args, opts);
151
122
  }
152
123
  if (mux === "git-bash-tmux") {
153
- const bash = findGitBashExe();
124
+ const bash = resolveGitBashExecutable();
154
125
  if (!bash) throw new Error("git-bash-tmux 감지 실패");
155
126
  const r = spawnSync(bash, ["-lc", `tmux ${args}`], {
156
127
  encoding: "utf8",
@@ -220,77 +191,6 @@ export function resolveAttachCommand(sessionName, opts = {}) {
220
191
  };
221
192
  }
222
193
 
223
- /**
224
- * Windows Terminal pane 포커스 이동
225
- * @param {number} paneIndex - createWtSession()에서 생성한 pane 인덱스(0 기반)
226
- * @param {object} opts
227
- * @param {'1xN'|'Nx1'} opts.layout
228
- * @returns {boolean}
229
- */
230
- export function focusWtPane(paneIndex, opts = {}) {
231
- // wt() 제거로 인해 일시 비활성화 (탭 기반 전환 필요)
232
- return false;
233
- }
234
-
235
- /**
236
- * Windows Terminal에서 생성한 팀 pane 정리
237
- * @param {object} opts
238
- * @param {'1xN'|'Nx1'} opts.layout
239
- * @param {number} opts.paneCount
240
- * @returns {number} 닫힌 pane 수 (best-effort)
241
- */
242
- export function closeWtSession(opts = {}) {
243
- // wt() 제거로 인해 일시 비활성화 (탭 기반 전환 필요)
244
- return 0;
245
- }
246
-
247
- /**
248
- * Windows Terminal 독립 모드 세션 생성 (비동기)
249
- * @param {string} sessionName
250
- * @param {object} opts
251
- * @param {'1xN'|'Nx1'} opts.layout
252
- * @param {Array<{title:string,command:string,cwd?:string}>} opts.paneCommands
253
- * @returns {Promise<{ sessionName: string, panes: string[], titles: string[], layout: '1xN'|'Nx1', paneCount: number, anchorPane: string }>}
254
- */
255
- export async function createWtSession(sessionName, opts = {}) {
256
- const { layout = "1xN", paneCommands = [] } = opts;
257
-
258
- if (!hasWindowsTerminalSession()) {
259
- throw new Error("WT_SESSION 미감지");
260
- }
261
- if (!getEnvironment().terminal.hasWt) {
262
- throw new Error("wt.exe 미발견");
263
- }
264
- if (!Array.isArray(paneCommands) || paneCommands.length === 0) {
265
- throw new Error("paneCommands가 비어 있음");
266
- }
267
-
268
- const wtManager = createWtManager();
269
- const panes = [];
270
- const titles = [];
271
-
272
- for (let i = 0; i < paneCommands.length; i++) {
273
- const pane = paneCommands[i] || {};
274
- const title = pane.title || `${sessionName}-${i + 1}`;
275
- const command = String(pane.command || "").trim();
276
- const cwd = pane.cwd || process.cwd();
277
- if (!command) continue;
278
-
279
- await wtManager.createTab({ title, command, cwd });
280
- panes.push(`wt:${i}`);
281
- titles.push(title);
282
- }
283
-
284
- return {
285
- sessionName,
286
- panes,
287
- titles,
288
- layout: layout === "Nx1" ? "Nx1" : "1xN",
289
- paneCount: panes.length,
290
- anchorPane: "wt:anchor",
291
- };
292
- }
293
-
294
194
  /**
295
195
  * tmux 세션 생성 + 레이아웃 분할
296
196
  * @param {string} sessionName — 세션 이름
@@ -402,23 +302,12 @@ export function configureTeammateKeybindings(sessionName, opts = {}) {
402
302
  ? `'select-pane -t :.- \\; resize-pane -Z'`
403
303
  : `'select-pane -t :.-'`;
404
304
 
405
- if (inProcess) {
406
- // 단일 뷰(zoom) 상태에서 팀메이트 순환
407
- tmux(
408
- `bind-key -T root -n S-Down if-shell -F '${cond}' ${bindNext} 'send-keys S-Down'`,
409
- );
410
- tmux(
411
- `bind-key -T root -n S-Up if-shell -F '${cond}' ${bindPrev} 'send-keys S-Up'`,
412
- );
413
- } else {
414
- // 분할 뷰에서 팀메이트 순환
415
- tmux(
416
- `bind-key -T root -n S-Down if-shell -F '${cond}' ${bindNext} 'send-keys S-Down'`,
417
- );
418
- tmux(
419
- `bind-key -T root -n S-Up if-shell -F '${cond}' ${bindPrev} 'send-keys S-Up'`,
420
- );
421
- }
305
+ tmux(
306
+ `bind-key -T root -n S-Down if-shell -F '${cond}' ${bindNext} 'send-keys S-Down'`,
307
+ );
308
+ tmux(
309
+ `bind-key -T root -n S-Up if-shell -F '${cond}' ${bindPrev} 'send-keys S-Up'`,
310
+ );
422
311
 
423
312
  // 대체 키: 일부 환경에서 S-Up이 누락될 때 사용
424
313
  tmux(
@@ -1,127 +1,5 @@
1
- // hub/team/swarm-intent.mjs — X-Intent helper utilities for semantic merge awareness
2
-
3
1
  const INTENT_TRAILER_REGEX = /^X-Intent:\s*(.+)$/m;
4
2
 
5
- function normalizeText(value) {
6
- return typeof value === "string" ? value.trim() : "";
7
- }
8
-
9
- function normalizeTouches(touches) {
10
- if (!Array.isArray(touches)) return null;
11
- return touches
12
- .filter((item) => typeof item === "string")
13
- .map((item) => item.trim().replace(/\\/g, "/"))
14
- .filter(Boolean);
15
- }
16
-
17
- function normalizeIntent(intentObj) {
18
- if (!intentObj || typeof intentObj !== "object" || Array.isArray(intentObj)) {
19
- return null;
20
- }
21
-
22
- const scope = normalizeText(intentObj.scope);
23
- const action = normalizeText(intentObj.action);
24
- const reason = normalizeText(intentObj.reason);
25
- const touches = normalizeTouches(intentObj.touches);
26
-
27
- if (!scope || !action || !reason || !touches) {
28
- return null;
29
- }
30
-
31
- return {
32
- scope,
33
- action,
34
- reason,
35
- touches,
36
- invariant: normalizeText(intentObj.invariant),
37
- conflictsWith: normalizeText(intentObj.conflictsWith),
38
- };
39
- }
40
-
41
- function fallbackIntent(llmResponse) {
42
- const responseText = String(llmResponse ?? "");
43
- return {
44
- scope: "unknown",
45
- action: "unknown",
46
- reason: responseText.slice(0, 100),
47
- touches: [],
48
- invariant: "",
49
- conflictsWith: "",
50
- };
51
- }
52
-
53
- function includesEitherWay(a, b) {
54
- if (!a || !b) return false;
55
- return a.includes(b) || b.includes(a);
56
- }
57
-
58
- function hasConflict(intentA, intentB) {
59
- const conflictsWith = normalizeText(intentA?.conflictsWith).toLowerCase();
60
- const scope = normalizeText(intentB?.scope).toLowerCase();
61
- const action = normalizeText(intentB?.action).toLowerCase();
62
-
63
- if (!conflictsWith) return false;
64
- return (
65
- includesEitherWay(conflictsWith, scope) ||
66
- includesEitherWay(conflictsWith, action)
67
- );
68
- }
69
-
70
- function getTouchesOverlap(intentA, intentB) {
71
- const touchesA = new Set(
72
- (normalizeTouches(intentA?.touches) ?? []).map((item) =>
73
- item.toLowerCase(),
74
- ),
75
- );
76
- const touchesB = (normalizeTouches(intentB?.touches) ?? []).map((item) =>
77
- item.toLowerCase(),
78
- );
79
- return touchesB.filter((item) => touchesA.has(item));
80
- }
81
-
82
- /**
83
- * Build prompts for an LLM to generate an X-Intent JSON object.
84
- * @param {string[]} changedFiles
85
- * @param {string} commitDiff
86
- * @param {string} taskContext
87
- * @returns {{ systemPrompt: string, userPrompt: string }}
88
- */
89
- export function generateIntentPrompt(changedFiles, commitDiff, taskContext) {
90
- const files = Array.isArray(changedFiles)
91
- ? changedFiles.filter((filePath) => typeof filePath === "string")
92
- : [];
93
- const diffSnippet = String(commitDiff ?? "").slice(0, 4000);
94
- const context = String(taskContext ?? "").trim();
95
-
96
- return {
97
- systemPrompt:
98
- "You generate commit intent metadata for semantic merge awareness. Return only valid JSON with keys: scope, action, reason, touches, invariant, conflictsWith.",
99
- userPrompt: [
100
- "Create an intent object from the following commit context.",
101
- "- scope/action/reason must be concise strings.",
102
- "- touches must be an array of touched file paths.",
103
- "- invariant/conflictsWith should be strings (empty if unknown).",
104
- "- Return JSON only (no markdown).",
105
- "",
106
- `Task context:\n${context || "(none provided)"}`,
107
- "",
108
- `Changed files (${files.length}):\n${files.join("\n") || "(none)"}`,
109
- "",
110
- "Commit diff (first 4000 chars):",
111
- diffSnippet || "(empty)",
112
- ].join("\n"),
113
- };
114
- }
115
-
116
- /**
117
- * Format an intent object into an X-Intent commit trailer.
118
- * @param {object} intentObj
119
- * @returns {string}
120
- */
121
- export function formatIntentTrailer(intentObj) {
122
- return `X-Intent: ${JSON.stringify(intentObj)}`;
123
- }
124
-
125
3
  /**
126
4
  * Parse an X-Intent trailer from a commit message.
127
5
  * @param {string} commitMessage
@@ -138,79 +16,3 @@ export function parseIntentTrailer(commitMessage) {
138
16
  return null;
139
17
  }
140
18
  }
141
-
142
- /**
143
- * Classify the relation between two intents.
144
- * @param {object} intentA
145
- * @param {object} intentB
146
- * @returns {{ relation: "complementary" | "complementary-risky" | "contradictory" | "independent", reason: string }}
147
- */
148
- export function classifyIntentPair(intentA, intentB) {
149
- if (hasConflict(intentA, intentB) || hasConflict(intentB, intentA)) {
150
- return {
151
- relation: "contradictory",
152
- reason:
153
- "conflictsWith on one intent matches the other intent scope/action",
154
- };
155
- }
156
-
157
- const scopeA = normalizeText(intentA?.scope).toLowerCase();
158
- const scopeB = normalizeText(intentB?.scope).toLowerCase();
159
-
160
- if (scopeA && scopeB && scopeA === scopeB) {
161
- const overlap = getTouchesOverlap(intentA, intentB);
162
- if (overlap.length > 0) {
163
- return {
164
- relation: "complementary-risky",
165
- reason: `same scope with overlapping touched files: ${overlap.join(", ")}`,
166
- };
167
- }
168
-
169
- return {
170
- relation: "complementary",
171
- reason:
172
- "same scope with no conflicting intent and no touched-file overlap",
173
- };
174
- }
175
-
176
- return {
177
- relation: "independent",
178
- reason: "different scopes and no conflictsWith match detected",
179
- };
180
- }
181
-
182
- /**
183
- * Build a validated intent object from raw LLM response text.
184
- * @param {string} llmResponse
185
- * @returns {{ scope: string, action: string, reason: string, touches: string[], invariant: string, conflictsWith: string }}
186
- */
187
- export function buildIntentFromLLMResponse(llmResponse) {
188
- const responseText = String(llmResponse ?? "").trim();
189
- const candidates = [];
190
-
191
- const codeBlockRegex = /```(?:json)?\s*([\s\S]*?)```/gi;
192
- for (const match of responseText.matchAll(codeBlockRegex)) {
193
- if (match[1]) candidates.push(match[1].trim());
194
- }
195
-
196
- candidates.push(responseText);
197
-
198
- const firstBrace = responseText.indexOf("{");
199
- const lastBrace = responseText.lastIndexOf("}");
200
- if (firstBrace >= 0 && lastBrace > firstBrace) {
201
- candidates.push(responseText.slice(firstBrace, lastBrace + 1));
202
- }
203
-
204
- for (const candidate of candidates) {
205
- if (!candidate) continue;
206
- try {
207
- const parsed = JSON.parse(candidate);
208
- const normalized = normalizeIntent(parsed);
209
- if (normalized) return normalized;
210
- } catch {
211
- // ignore parse failures and continue trying other candidates
212
- }
213
- }
214
-
215
- return fallbackIntent(responseText);
216
- }
@@ -56,16 +56,8 @@ function execOpenTerminal(execFn) {
56
56
  });
57
57
  }
58
58
 
59
- // psmux는 Windows에서 wt-manager 경유 (createTab/splitPane 등) — tmux 호환 unix 명령군과 다른 표면.
60
- // 비-Windows(macOS/Linux) 환경에서는 psmux가 tmux-compatible new-window/select-pane 명령을 받아주므로
61
- // tmux 어댑터와 동일하게 취급한다.
62
- function isTmuxLikeMux(mux, platform) {
63
- return TMUX_LIKE_MUXES.has(mux) || (platform !== "win32" && mux === "psmux");
64
- }
65
-
66
- function shellCommandName(value) {
67
- const command = String(value);
68
- return /^[A-Za-z0-9_./:-]+$/u.test(command) ? command : shellQuote(command);
59
+ function isTmuxLikeMux(mux) {
60
+ return TMUX_LIKE_MUXES.has(mux);
69
61
  }
70
62
 
71
63
  function powershellCommandName(value) {
@@ -94,17 +86,6 @@ function defaultPsmuxBinaryExists(command) {
94
86
  }
95
87
  }
96
88
 
97
- function buildAttachCommand(mux, sessionName, deps = {}) {
98
- if (mux === "psmux") {
99
- const command = process.env.PSMUX_BIN || "psmux";
100
- const psmuxBinaryExists =
101
- deps.psmuxBinaryExists || defaultPsmuxBinaryExists;
102
- if (!psmuxBinaryExists(command)) return null;
103
- return `${shellCommandName(command)} attach-session -t ${shellQuote(sessionName)}`;
104
- }
105
- return `tmux attach-session -t ${shellQuote(sessionName)}`;
106
- }
107
-
108
89
  // wt-manager.createTab은 (a) undefined/void 반환 — legacy success 의미 (b) {success:true|false, ...} 객체 반환의 두 형태가 공존.
109
90
  // `result?.success !== false`는 undefined/null/{}을 모두 success로 취급하고 명시적 {success:false}만 실패로 본다.
110
91
  function wtResultSucceeded(result) {
@@ -162,7 +143,7 @@ export function createTerminalOpener(deps = {}) {
162
143
  }
163
144
 
164
145
  const mux = resolveMux(deps);
165
- if (isTmuxLikeMux(mux, platform)) {
146
+ if (isTmuxLikeMux(mux)) {
166
147
  const targetPane = resolveLeadPane(spec.targetPane);
167
148
  if (!targetPane) return false;
168
149
  const targetWindow = resolveLeadWindow(tmuxExec, targetPane);
@@ -212,19 +193,14 @@ export function createTerminalOpener(deps = {}) {
212
193
  }
213
194
 
214
195
  const mux = resolveMux(deps);
215
- if (isTmuxLikeMux(mux, platform)) {
196
+ if (isTmuxLikeMux(mux)) {
216
197
  const targetPane = resolveLeadPane(opts.targetPane);
217
198
  if (!targetPane) return false;
218
- const socketPath =
219
- mux === "psmux" ? null : resolveLeadSocket(tmuxExec, targetPane);
220
- if (mux !== "psmux" && !socketPath) return false;
221
- const attachCommand =
222
- mux === "psmux"
223
- ? buildAttachCommand(mux, sessionName, deps)
224
- : `env -u TMUX tmux -S ${shellQuote(
225
- socketPath,
226
- )} attach-session -t ${shellQuote(sessionName)}`;
227
- if (!attachCommand) return false;
199
+ const socketPath = resolveLeadSocket(tmuxExec, targetPane);
200
+ if (!socketPath) return false;
201
+ const attachCommand = `env -u TMUX tmux -S ${shellQuote(
202
+ socketPath,
203
+ )} attach-session -t ${shellQuote(sessionName)}`;
228
204
  const targetWindow = resolveLeadWindow(tmuxExec, targetPane);
229
205
  if (!targetWindow) return false;
230
206
  try {
@@ -246,12 +222,12 @@ export function createTerminalOpener(deps = {}) {
246
222
  const target = `${sessionName}:0.${workerNumber}`;
247
223
  const mux = resolveMux(deps);
248
224
 
249
- if (mux === "psmux") {
225
+ if (platform === "win32" && mux === "psmux") {
250
226
  psmuxExec(["select-pane", "-t", target]);
251
227
  return true;
252
228
  }
253
229
 
254
- if (isTmuxLikeMux(mux, platform)) {
230
+ if (isTmuxLikeMux(mux)) {
255
231
  tmuxExec(`select-pane -t ${shellQuote(target)}`);
256
232
  return true;
257
233
  }
@@ -195,7 +195,7 @@ export function normalizeWorkerState(existing = {}, state = {}, opts = {}) {
195
195
 
196
196
  // ── 색상 헬퍼 ─────────────────────────────────────────────────────────────
197
197
  export function cliColor(cli) {
198
- if (cli === "gemini") return FG.gemini;
198
+ if (cli === "antigravity" || cli === "agy") return FG.antigravity;
199
199
  if (cli === "claude") return FG.claude;
200
200
  if (cli === "codex") return FG.codex;
201
201
  return FG.white;
@@ -358,8 +358,8 @@ function ingest() {
358
358
 
359
359
  // CLI 타입 감지
360
360
  let cli = "codex";
361
- if (pane.title.includes("gemini") || pane.title.includes("🔵"))
362
- cli = "gemini";
361
+ if (pane.title.includes("antigravity") || pane.title.includes("🔵"))
362
+ cli = "antigravity";
363
363
  else if (pane.title.includes("claude") || pane.title.includes("🟠"))
364
364
  cli = "claude";
365
365
  ws.title = pane.title;