@triflux/remote 10.50.0 → 10.50.2

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.
@@ -29,8 +29,8 @@ import { IS_WINDOWS } from "@triflux/core/hub/platform.mjs";
29
29
  // OS 별 primary multiplexer 를 명시적으로 결정한다.
30
30
  // - PSMUX_BIN env 가 있으면 user override (가장 우선)
31
31
  // - Windows: psmux 가 primary. PATH + 기본 설치 경로에서 탐색.
32
- // - mac/Linux: tmux 가 primary. PATH 에서만 찾는다 (HomeBrew, apt 등 표준 위치).
33
- // silent fallback 은 두지 않는다 — primary 가 없으면 의도된 fail.
32
+ // - mac/Linux: tmux 가 primary. 탐색 없이 PATH 의 tmux 를 쓴다.
33
+ // silent fallback 은 두지 않는다. primary 가 없으면 의도된 fail.
34
34
  const PSMUX_BIN = (() => {
35
35
  if (process.env.PSMUX_BIN) return process.env.PSMUX_BIN;
36
36
 
@@ -58,20 +58,9 @@ const PSMUX_BIN = (() => {
58
58
  return "psmux"; // 최종 — hasPsmux/hasMultiplexer 가 검증
59
59
  }
60
60
 
61
- // mac/Linux primary: tmux (POSIX 표준). silent fallback 아님 — 정식 primary.
62
- try {
63
- childProcess.execFileSync("tmux", ["-V"], {
64
- stdio: "ignore",
65
- timeout: 2000,
66
- });
67
- return "tmux";
68
- } catch {
69
- /* tmux not in PATH — hasMultiplexer 가 false 반환할 것 */
70
- }
61
+ // mac/Linux primary: tmux
71
62
  return "tmux";
72
63
  })();
73
- const GIT_BASH =
74
- process.env.GIT_BASH_PATH || resolveGitBashExecutable() || "bash";
75
64
 
76
65
  /** Windows psmux 세션의 기본 셸을 PowerShell로 강제한다 (pwsh7 우선, ps5 fallback). */
77
66
  const PWSH_BIN = (() => {
@@ -231,10 +220,10 @@ function randomToken(prefix) {
231
220
  }
232
221
 
233
222
  function ensurePsmuxInstalled() {
234
- if (!hasPsmux()) {
223
+ if (!hasMultiplexer()) {
224
+ const mux = IS_WINDOWS ? "psmux" : "tmux";
235
225
  throw new Error(
236
- "psmux가 설치되어 있지 않습니다.\n\n" +
237
- "psmux는 Codex/Antigravity CLI를 병렬 세션으로 실행하는 터미널 멀티플렉서입니다.\n" +
226
+ `${mux}가 설치되어 있지 않습니다.\n\n` +
238
227
  "설치 방법 (택 1):\n" +
239
228
  `${formatPsmuxInstallGuidance(" ")}\n\n` +
240
229
  "설치 후 터미널을 재시작하세요.",
@@ -539,13 +528,6 @@ function disablePipeCapture(paneId) {
539
528
  }
540
529
  }
541
530
 
542
- function sendLiteralToPane(paneId, text, submit = true) {
543
- psmuxExec(["send-keys", "-t", paneId, "-l", text]);
544
- if (submit) {
545
- psmuxExec(["send-keys", "-t", paneId, "Enter"]);
546
- }
547
- }
548
-
549
531
  export function sendKeysToPane(paneId, text, submit = true) {
550
532
  psmuxExec(["send-keys", "-t", paneId, "-l", text]);
551
533
  if (submit) {
@@ -563,10 +545,6 @@ function toPatternRegExp(pattern) {
563
545
  return new RegExp(String(pattern), "m");
564
546
  }
565
547
 
566
- function escapeRegExp(value) {
567
- return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
568
- }
569
-
570
548
  function psmux(args, opts = {}) {
571
549
  const normalizedArgs = normalizePsmuxArgs(args);
572
550
  // PSMUX_SESSION 제거 — 기존 psmux 세션 내에서 호출 시 중첩 세션 차단 방지
@@ -614,7 +592,7 @@ export function hasMultiplexer() {
614
592
 
615
593
  /**
616
594
  * backward-compat alias — historical 이름 `hasPsmux` 는 OS 별 primary 의 의미를
617
- * 가린다. 외부 caller (scripts/remote-spawn.mjs, scripts/session-spawn-helper.mjs,
595
+ * 가린다. 외부 caller (scripts/remote-spawn.mjs,
618
596
  * tests) 호환을 위해 alias 로 보존. 신규 코드는 hasMultiplexer 사용.
619
597
  */
620
598
  export const hasPsmux = hasMultiplexer;
@@ -759,7 +737,7 @@ export function createPsmuxSession(sessionName, opts = {}) {
759
737
  "$OutputEncoding = [System.Text.UTF8Encoding]::new($false)",
760
738
  ].join("; ");
761
739
  panes.forEach((paneId) => {
762
- sendLiteralToPane(paneId, encodingInit, true);
740
+ sendKeysToPane(paneId, encodingInit, true);
763
741
  });
764
742
  }
765
743
 
@@ -1048,22 +1026,6 @@ function detachAttachedClients(sessionName, waitMs = 750) {
1048
1026
  }
1049
1027
  }
1050
1028
 
1051
- function findFallbackPane(sessionName, excludedPaneId) {
1052
- try {
1053
- const panes = listPaneDetails(sessionName).filter(
1054
- (pane) => pane.paneId !== excludedPaneId && !pane.isDead,
1055
- );
1056
- if (panes.length === 0) return null;
1057
- return (
1058
- panes.find((pane) => pane.title === "lead") ||
1059
- panes.find((pane) => pane.paneIndex === 0) ||
1060
- panes[0]
1061
- );
1062
- } catch {
1063
- return null;
1064
- }
1065
- }
1066
-
1067
1029
  /**
1068
1030
  * psmux 세션 종료
1069
1031
  * 순서: pipe-pane 해제 → pane 프로세스 트리 정리 → 세션 종료 → 고아 정리
@@ -1179,7 +1141,7 @@ export function pruneStale(opts = {}) {
1179
1141
  const result = killSessionByTitle(
1180
1142
  new RegExp(
1181
1143
  `^(?:${sessions
1182
- .map((session) => escapeRegExp(session.title))
1144
+ .map((session) => escapeRegex(session.title))
1183
1145
  .join("|")})$`,
1184
1146
  ),
1185
1147
  );
@@ -1496,7 +1458,7 @@ export function dispatchCommand(sessionName, paneNameOrTarget, commandText) {
1496
1458
  wrapped = `{ ${safeCommand}; __ec=$?; echo "${COMPLETION_PREFIX}${token}:$__ec"; }`;
1497
1459
  }
1498
1460
 
1499
- sendLiteralToPane(pane.paneId, wrapped, true);
1461
+ sendKeysToPane(pane.paneId, wrapped, true);
1500
1462
 
1501
1463
  return { paneId: pane.paneId, paneName, token, logPath };
1502
1464
  }
@@ -1677,7 +1639,7 @@ export async function waitForCompletion(
1677
1639
  opts = {},
1678
1640
  ) {
1679
1641
  const completionRegex = new RegExp(
1680
- `${escapeRegExp(COMPLETION_PREFIX)}${escapeRegExp(token)}:(\\d+)`,
1642
+ `${escapeRegex(COMPLETION_PREFIX)}${escapeRegex(token)}:(\\d+)`,
1681
1643
  "m",
1682
1644
  );
1683
1645
  const result = await waitForPattern(
@@ -1726,211 +1688,10 @@ export async function waitForCompletion(
1726
1688
  };
1727
1689
  }
1728
1690
 
1729
- // ─── 하이브리드 모드 워커 관리 함수 ───
1730
-
1731
- /**
1732
- * psmux 세션의 새 pane에서 워커 실행
1733
- * @param {string} sessionName - 대상 psmux 세션 이름
1734
- * @param {string} workerName - 워커 식별용 pane 타이틀
1735
- * @param {string} cmd - 실행할 커맨드
1736
- * @returns {{ paneId: string, workerName: string }}
1737
- */
1738
- export function spawnWorker(sessionName, workerName, cmd) {
1739
- if (!hasPsmux()) {
1740
- throw new Error(
1741
- "psmux가 설치되어 있지 않습니다.\n" +
1742
- `설치 방법:\n${formatPsmuxInstallGuidance(" ")}`,
1743
- );
1744
- }
1745
-
1746
- // remain-on-exit: 종료된 pane이 즉시 사라지는 것 방지
1747
- try {
1748
- psmuxExec(["set-option", "-t", sessionName, "remain-on-exit", "on"]);
1749
- } catch {
1750
- // 미지원 시 무시
1751
- }
1752
-
1753
- // Windows: pane 기본셸이 PowerShell → Git Bash로 래핑
1754
- // psmux가 이스케이프 시퀀스를 처리하므로 포워드 슬래시 경로를 사용한다.
1755
- const shellCmd = IS_WINDOWS
1756
- ? `& '${GIT_BASH.replace(/\\/g, "/")}' -l -c '${cmd.replace(/'/g, "'\\''")}'`
1757
- : cmd;
1758
-
1759
- try {
1760
- const paneTarget = psmuxExec([
1761
- "split-window",
1762
- "-t",
1763
- sessionName,
1764
- "-P",
1765
- "-F",
1766
- "#{session_name}:#{window_index}.#{pane_index}",
1767
- shellCmd,
1768
- ]);
1769
- try {
1770
- psmuxExec(["select-pane", "-t", paneTarget, "-T", workerName]);
1771
- } catch {
1772
- // tmux 2.6 미만: select-pane -T 미지원 — 무시
1773
- }
1774
- return { paneId: paneTarget, workerName };
1775
- } catch (err) {
1776
- throw new Error(
1777
- `워커 생성 실패 (session=${sessionName}, worker=${workerName}): ${err.message}`,
1778
- );
1779
- }
1780
- }
1781
-
1782
- /**
1783
- * 워커 pane 실행 상태 확인
1784
- * @param {string} sessionName - 대상 psmux 세션 이름
1785
- * @param {string} workerName - 워커 pane 타이틀
1786
- * @returns {{ status: "running"|"exited", exitCode: number|null, paneId: string }}
1787
- */
1788
- export function getWorkerStatus(sessionName, workerName) {
1789
- if (!hasPsmux()) {
1790
- throw new Error(
1791
- `psmux 미설치. 설치 방법:\n${formatPsmuxInstallGuidance(" ")}`,
1792
- );
1793
- }
1794
- try {
1795
- const pane = resolvePane(sessionName, workerName);
1796
- return {
1797
- status: pane.isDead ? "exited" : "running",
1798
- exitCode: pane.isDead ? pane.exitCode : null,
1799
- paneId: pane.paneId,
1800
- };
1801
- } catch (err) {
1802
- if (err.message.includes("Pane을 찾을 수 없습니다")) {
1803
- throw new Error(`워커를 찾을 수 없습니다: ${workerName}`);
1804
- }
1805
- throw new Error(
1806
- `워커 상태 조회 실패 (session=${sessionName}, worker=${workerName}): ${err.message}`,
1807
- );
1808
- }
1809
- }
1810
-
1811
- /**
1812
- * 워커 pane 프로세스 강제 종료
1813
- * @param {string} sessionName - 대상 psmux 세션 이름
1814
- * @param {string} workerName - 워커 pane 타이틀
1815
- * @returns {{ killed: boolean }}
1816
- */
1817
- export function killWorker(sessionName, workerName) {
1818
- if (!hasPsmux()) {
1819
- throw new Error(
1820
- `psmux 미설치. 설치 방법:\n${formatPsmuxInstallGuidance(" ")}`,
1821
- );
1822
- }
1823
- try {
1824
- const { paneId, status } = getWorkerStatus(sessionName, workerName);
1825
- const attachedCount = getPsmuxSessionAttachedCount(sessionName);
1826
- const fallbackPane =
1827
- attachedCount > 0 ? findFallbackPane(sessionName, paneId) : null;
1828
-
1829
- if (fallbackPane?.paneId) {
1830
- try {
1831
- psmuxExec(["select-pane", "-t", fallbackPane.paneId]);
1832
- } catch {
1833
- // focus 회복 best-effort
1834
- }
1835
- }
1836
-
1837
- // pipe-pane 캡처 해제 — reader 프로세스 정상 종료 유도
1838
- disablePipeCapture(paneId);
1839
-
1840
- // pane PID 수집 → 프로세스 트리 정리 (MCP 서버 좀비 방지)
1841
- try {
1842
- const pidOutput = psmuxExec([
1843
- "list-panes",
1844
- "-t",
1845
- paneId,
1846
- "-F",
1847
- "#{pane_pid}",
1848
- ]);
1849
- const pid = Number.parseInt(pidOutput.trim(), 10);
1850
- if (Number.isFinite(pid) && pid > 0) killProcessTree(pid);
1851
- } catch {
1852
- // PID 조회 실패 — 아래에서 pane만 정리
1853
- }
1854
-
1855
- // 이미 종료된 워커 → pane 정리만 수행
1856
- if (status === "exited") {
1857
- try {
1858
- psmuxExec(["kill-pane", "-t", paneId]);
1859
- } catch {
1860
- // 무시
1861
- }
1862
- return { killed: true };
1863
- }
1864
-
1865
- // running → C-c 우아한 종료 시도
1866
- try {
1867
- psmuxExec(["send-keys", "-t", paneId, "C-c"]);
1868
- } catch {
1869
- // send-keys 실패 무시
1870
- }
1871
-
1872
- if (!fallbackPane) {
1873
- try {
1874
- psmuxExec(["send-keys", "-t", paneId, "exit", "Enter"]);
1875
- } catch {
1876
- // send-keys 실패 무시
1877
- }
1878
- }
1879
-
1880
- sleepMs(2000);
1881
-
1882
- try {
1883
- psmuxExec(["kill-pane", "-t", paneId]);
1884
- } catch {
1885
- // 이미 종료된 pane — 무시
1886
- }
1887
-
1888
- if (fallbackPane?.paneId) {
1889
- try {
1890
- psmuxExec(["select-pane", "-t", fallbackPane.paneId]);
1891
- } catch {
1892
- // pane 정리 후 focus 재선택 best-effort
1893
- }
1894
- }
1895
- return { killed: true };
1896
- } catch (err) {
1897
- if (err.message.includes("워커를 찾을 수 없습니다")) {
1898
- return { killed: true };
1899
- }
1900
- throw new Error(
1901
- `워커 종료 실패 (session=${sessionName}, worker=${workerName}): ${err.message}`,
1902
- );
1903
- }
1904
- }
1905
-
1906
- /**
1907
- * 워커 pane 출력 마지막 N줄 캡처
1908
- * @param {string} sessionName - 대상 psmux 세션 이름
1909
- * @param {string} workerName - 워커 pane 타이틀
1910
- * @param {number} lines - 캡처할 줄 수 (기본 50)
1911
- * @returns {string} 캡처된 출력
1912
- */
1913
- export function captureWorkerOutput(sessionName, workerName, lines = 50) {
1914
- if (!hasPsmux()) {
1915
- throw new Error(
1916
- `psmux 미설치. 설치 방법:\n${formatPsmuxInstallGuidance(" ")}`,
1917
- );
1918
- }
1919
- try {
1920
- const { paneId } = getWorkerStatus(sessionName, workerName);
1921
- return psmuxExec(["capture-pane", "-t", paneId, "-p", "-S", `-${lines}`]);
1922
- } catch (err) {
1923
- if (err.message.includes("워커를 찾을 수 없습니다")) throw err;
1924
- throw new Error(
1925
- `출력 캡처 실패 (session=${sessionName}, worker=${workerName}): ${err.message}`,
1926
- );
1927
- }
1928
- }
1929
-
1930
1691
  // ─── CLI 진입점 ───
1931
1692
 
1932
1693
  if (process.argv[1]?.endsWith("psmux.mjs")) {
1933
- (async () => {
1694
+ (() => {
1934
1695
  const rawArgs = process.argv.slice(2);
1935
1696
  const internalMode = rawArgs[0] === "--internal";
1936
1697
  const cmd = internalMode ? rawArgs[1] : rawArgs[0];
@@ -2007,157 +1768,10 @@ if (process.argv[1]?.endsWith("psmux.mjs")) {
2007
1768
  return;
2008
1769
  }
2009
1770
 
2010
- switch (cmd) {
2011
- case "spawn": {
2012
- const session = getArg("session");
2013
- const name = getArg("name");
2014
- const workerCmd = getArg("cmd");
2015
- if (!session || !name || !workerCmd) {
2016
- console.error(
2017
- "사용법: node psmux.mjs spawn --session <세션> --name <워커명> --cmd <커맨드>",
2018
- );
2019
- process.exit(1);
2020
- }
2021
- console.log(
2022
- JSON.stringify(spawnWorker(session, name, workerCmd), null, 2),
2023
- );
2024
- break;
2025
- }
2026
- case "status": {
2027
- const session = getArg("session");
2028
- const name = getArg("name");
2029
- if (!session || !name) {
2030
- console.error(
2031
- "사용법: node psmux.mjs status --session <세션> --name <워커명>",
2032
- );
2033
- process.exit(1);
2034
- }
2035
- console.log(JSON.stringify(getWorkerStatus(session, name), null, 2));
2036
- break;
2037
- }
2038
- case "kill": {
2039
- const session = getArg("session");
2040
- const name = getArg("name");
2041
- if (!session || !name) {
2042
- console.error(
2043
- "사용법: node psmux.mjs kill --session <세션> --name <워커명>",
2044
- );
2045
- process.exit(1);
2046
- }
2047
- console.log(JSON.stringify(killWorker(session, name), null, 2));
2048
- break;
2049
- }
2050
- case "output": {
2051
- const session = getArg("session");
2052
- const name = getArg("name");
2053
- const lines = parseInt(getArg("lines") || "50", 10);
2054
- if (!session || !name) {
2055
- console.error(
2056
- "사용법: node psmux.mjs output --session <세션> --name <워커명> [--lines <줄수>]",
2057
- );
2058
- process.exit(1);
2059
- }
2060
- console.log(captureWorkerOutput(session, name, lines));
2061
- break;
2062
- }
2063
- case "capture-start": {
2064
- const session = getArg("session");
2065
- const name = getArg("name");
2066
- if (!session || !name) {
2067
- console.error(
2068
- "사용법: node psmux.mjs capture-start --session <세션> --name <pane>",
2069
- );
2070
- process.exit(1);
2071
- }
2072
- console.log(JSON.stringify(startCapture(session, name), null, 2));
2073
- break;
2074
- }
2075
- case "dispatch": {
2076
- const session = getArg("session");
2077
- const name = getArg("name");
2078
- const commandText = getArg("command");
2079
- if (!session || !name || !commandText) {
2080
- console.error(
2081
- "사용법: node psmux.mjs dispatch --session <세션> --name <pane> --command <PowerShell 명령>",
2082
- );
2083
- process.exit(1);
2084
- }
2085
- console.log(
2086
- JSON.stringify(
2087
- dispatchCommand(session, name, commandText),
2088
- null,
2089
- 2,
2090
- ),
2091
- );
2092
- break;
2093
- }
2094
- case "wait-pattern": {
2095
- const session = getArg("session");
2096
- const name = getArg("name");
2097
- const pattern = getArg("pattern");
2098
- const timeoutSec = parseInt(getArg("timeout") || "300", 10);
2099
- if (!session || !name || !pattern) {
2100
- console.error(
2101
- "사용법: node psmux.mjs wait-pattern --session <세션> --name <pane> --pattern <정규식> [--timeout <초>]",
2102
- );
2103
- process.exit(1);
2104
- }
2105
- const result = await waitForPattern(
2106
- session,
2107
- name,
2108
- pattern,
2109
- timeoutSec,
2110
- );
2111
- console.log(JSON.stringify(result, null, 2));
2112
- if (!result.matched) process.exit(2);
2113
- break;
2114
- }
2115
- case "wait-completion": {
2116
- const session = getArg("session");
2117
- const name = getArg("name");
2118
- const token = getArg("token");
2119
- const timeoutSec = parseInt(getArg("timeout") || "300", 10);
2120
- if (!session || !name || !token) {
2121
- console.error(
2122
- "사용법: node psmux.mjs wait-completion --session <세션> --name <pane> --token <토큰> [--timeout <초>]",
2123
- );
2124
- process.exit(1);
2125
- }
2126
- const result = await waitForCompletion(
2127
- session,
2128
- name,
2129
- token,
2130
- timeoutSec,
2131
- );
2132
- console.log(JSON.stringify(result, null, 2));
2133
- if (!result.matched) process.exit(2);
2134
- break;
2135
- }
2136
- default:
2137
- console.error(
2138
- "사용법: node psmux.mjs spawn|status|kill|output|capture-start|dispatch|wait-pattern|wait-completion [args]",
2139
- );
2140
- console.error("");
2141
- console.error(
2142
- " spawn --session <세션> --name <워커명> --cmd <커맨드>",
2143
- );
2144
- console.error(" status --session <세션> --name <워커명>");
2145
- console.error(" kill --session <세션> --name <워커명>");
2146
- console.error(
2147
- " output --session <세션> --name <워커명> [--lines <줄수>]",
2148
- );
2149
- console.error(" capture-start --session <세션> --name <pane>");
2150
- console.error(
2151
- " dispatch --session <세션> --name <pane> --command <PowerShell 명령>",
2152
- );
2153
- console.error(
2154
- " wait-pattern --session <세션> --name <pane> --pattern <정규식> [--timeout <초>]",
2155
- );
2156
- console.error(
2157
- " wait-completion --session <세션> --name <pane> --token <토큰> [--timeout <초>]",
2158
- );
2159
- process.exit(1);
2160
- }
1771
+ console.error(
1772
+ "사용법: node psmux.mjs --internal list|kill-by-title|prune-stale [args]",
1773
+ );
1774
+ process.exit(1);
2161
1775
  } catch (err) {
2162
1776
  console.error(`오류: ${err.message}`);
2163
1777
  process.exit(1);
@@ -55,25 +55,6 @@ function hasGitBashTmux() {
55
55
  }
56
56
  }
57
57
 
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
58
  function isPsmuxCommandName(command) {
78
59
  const name = String(command || "")
79
60
  .replace(/\\/g, "/")
@@ -83,10 +64,6 @@ function isPsmuxCommandName(command) {
83
64
  return name === "psmux" || name === "psmux.exe" || name === "psmux.cmd";
84
65
  }
85
66
 
86
- function hasLiteralPsmuxBinary(command = process.env.PSMUX_BIN || "psmux") {
87
- return isPsmuxVersion(command);
88
- }
89
-
90
67
  /**
91
68
  * 터미널 멀티플렉서 감지 (결과 캐싱 — 프로세스 수명 동안 불변)
92
69
  * @returns {'tmux'|'git-bash-tmux'|'psmux'|null}
@@ -105,10 +82,6 @@ export function detectMultiplexer() {
105
82
  _cachedMux = "tmux";
106
83
  return _cachedMux;
107
84
  }
108
- if (hasLiteralPsmuxBinary()) {
109
- _cachedMux = "psmux";
110
- return _cachedMux;
111
- }
112
85
  _cachedMux = null;
113
86
  return _cachedMux;
114
87
  }
@@ -150,7 +123,7 @@ function tmux(args, opts = {}) {
150
123
  return psmuxExec(args, opts);
151
124
  }
152
125
  if (mux === "git-bash-tmux") {
153
- const bash = findGitBashExe();
126
+ const bash = resolveGitBashExecutable();
154
127
  if (!bash) throw new Error("git-bash-tmux 감지 실패");
155
128
  const r = spawnSync(bash, ["-lc", `tmux ${args}`], {
156
129
  encoding: "utf8",
@@ -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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@triflux/remote",
3
- "version": "10.50.0",
3
+ "version": "10.50.2",
4
4
  "description": "triflux remote — team mode, psmux, MCP workers, SQLite store.",
5
5
  "type": "module",
6
6
  "main": "hub/index.mjs",
@@ -59,7 +59,7 @@ const LEGACY_DEFAULT_MODELS = new Set(["Gemini 3.5 Flash (Medium)"]);
59
59
  // ── 용도별 effort (SSOT) ──
60
60
  // 한 세대(3.8 Flash)만 쓰고 역할에 따라 effort 를 나눈다. 키는 tfx 에이전트/역할 이름
61
61
  // (agent-map.json 의 에이전트, tfx multi --assign 의 role). bash(tfx-route.sh)와
62
- // node(execution-mode, gemini-adapter, cli-agy)가 모두 이 표를 읽는다.
62
+ // 실행 모드와 cli-agy가 이 표를 공유한다.
63
63
  // High : 판단이 결과를 좌우하는 역할 (검토, 설계, 디버깅, 분석)
64
64
  // Medium : 생성/작성/실행 (기본값)
65
65
  // Low : 분류, 요약, 추출, 번역처럼 짧고 기계적인 작업