@triflux/remote 10.1.0 → 10.25.1

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 (155) hide show
  1. package/hub/pipe.mjs +239 -121
  2. package/hub/public/dashboard.html +61 -0
  3. package/hub/server.mjs +2165 -708
  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 +84 -17
  9. package/hub/team/build-worker-prompt.mjs +42 -0
  10. package/hub/team/check-mcp-hub.mjs +49 -0
  11. package/hub/team/claude-daemon-control.mjs +219 -0
  12. package/hub/team/claude-native-bridge.mjs +784 -0
  13. package/hub/team/claude-session-projection.mjs +65 -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 +154 -33
  23. package/hub/team/cli/commands/start/parse-args.mjs +89 -12
  24. package/hub/team/cli/commands/start/start-headless.mjs +125 -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/index.mjs +2 -2
  33. package/hub/team/cli/manifest.mjs +38 -8
  34. package/hub/team/cli/render.mjs +52 -30
  35. package/hub/team/cli/services/attach-fallback.mjs +74 -54
  36. package/hub/team/cli/services/hub-client.mjs +44 -16
  37. package/hub/team/cli/services/member-selector.mjs +37 -30
  38. package/hub/team/cli/services/native-control.mjs +144 -116
  39. package/hub/team/cli/services/runtime-mode.mjs +55 -1
  40. package/hub/team/cli/services/state-store.mjs +66 -9
  41. package/hub/team/cli/services/task-model.mjs +40 -30
  42. package/hub/team/codex-review.mjs +521 -0
  43. package/hub/team/conductor-mesh-bridge.mjs +25 -24
  44. package/hub/team/conductor-registry.mjs +121 -0
  45. package/hub/team/conductor.mjs +1379 -671
  46. package/hub/team/consensus-meta.mjs +176 -0
  47. package/hub/team/dashboard-anchor.mjs +4 -5
  48. package/hub/team/dashboard-layout.mjs +3 -1
  49. package/hub/team/dashboard-open.mjs +20 -118
  50. package/hub/team/dashboard.mjs +76 -28
  51. package/hub/team/event-log.mjs +18 -10
  52. package/hub/team/execution-mode.mjs +241 -0
  53. package/hub/team/extract-completion-payload.mjs +110 -0
  54. package/hub/team/git-preflight.mjs +286 -0
  55. package/hub/team/handoff.mjs +49 -18
  56. package/hub/team/headless-bridge-session.mjs +17 -0
  57. package/hub/team/headless.mjs +2065 -1156
  58. package/hub/team/health-probe.mjs +232 -69
  59. package/hub/team/launcher-template.mjs +23 -13
  60. package/hub/team/lead-control.mjs +91 -4
  61. package/hub/team/mcp-selector.mjs +145 -0
  62. package/hub/team/native-supervisor.mjs +90 -26
  63. package/hub/team/native.mjs +74 -35
  64. package/hub/team/nativeProxy.mjs +184 -113
  65. package/hub/team/notify.mjs +147 -78
  66. package/hub/team/orchestrator.mjs +173 -161
  67. package/hub/team/pane.mjs +43 -15
  68. package/hub/team/process-cleanup.mjs +69 -25
  69. package/hub/team/psmux.mjs +1020 -279
  70. package/hub/team/recovery-store.mjs +149 -0
  71. package/hub/team/remote-probe.mjs +68 -52
  72. package/hub/team/remote-session.mjs +131 -59
  73. package/hub/team/remote-watcher.mjs +61 -33
  74. package/hub/team/retry-state-machine.mjs +267 -0
  75. package/hub/team/routing.mjs +51 -25
  76. package/hub/team/runtime-strategy.mjs +200 -0
  77. package/hub/team/schemas/worker-completion.json +39 -0
  78. package/hub/team/sentinel-capture.mjs +127 -0
  79. package/hub/team/session-sync.mjs +153 -6
  80. package/hub/team/session.mjs +605 -611
  81. package/hub/team/shared.mjs +13 -13
  82. package/hub/team/staleState.mjs +72 -30
  83. package/hub/team/swarm-cli.mjs +390 -0
  84. package/hub/team/swarm-hypervisor.mjs +1522 -193
  85. package/hub/team/swarm-intent.mjs +216 -0
  86. package/hub/team/swarm-locks.mjs +140 -25
  87. package/hub/team/swarm-planner.mjs +121 -21
  88. package/hub/team/swarm-preflight.mjs +317 -0
  89. package/hub/team/swarm-reconciler.mjs +128 -23
  90. package/hub/team/synapse-cli.mjs +458 -0
  91. package/hub/team/synapse-http.mjs +60 -0
  92. package/hub/team/synapse-registry.mjs +329 -0
  93. package/hub/team/terminal-opener.mjs +216 -0
  94. package/hub/team/tui-core.mjs +288 -0
  95. package/hub/team/tui-lite.mjs +234 -146
  96. package/hub/team/tui-remote-adapter.mjs +18 -34
  97. package/hub/team/tui-synapse.mjs +223 -0
  98. package/hub/team/tui-viewer.mjs +104 -44
  99. package/hub/team/tui-widgets.mjs +262 -0
  100. package/hub/team/tui.mjs +927 -437
  101. package/hub/team/worker-completion-validator.mjs +84 -0
  102. package/hub/team/worker-sandbox.mjs +93 -0
  103. package/hub/team/worker-signal.mjs +263 -0
  104. package/hub/team/worker-signal.types.d.ts +52 -0
  105. package/hub/team/worktree-lifecycle.mjs +517 -63
  106. package/hub/team/wt-manager.mjs +297 -26
  107. package/hub/tools.mjs +474 -252
  108. package/hub/tray.mjs +411 -368
  109. package/hub/workers/claude-worker.mjs +162 -118
  110. package/hub/workers/codex-app-server-worker.mjs +1015 -0
  111. package/hub/workers/codex-mcp.mjs +724 -507
  112. package/hub/workers/delegator-mcp.mjs +662 -335
  113. package/hub/workers/factory.mjs +100 -21
  114. package/hub/workers/gemini-worker.mjs +179 -86
  115. package/hub/workers/interface.mjs +23 -2
  116. package/hub/workers/lib/jsonrpc-stdio.mjs +463 -0
  117. package/hub/workers/worker-utils.mjs +22 -16
  118. package/package.json +4 -3
  119. package/scripts/lib/agent-json.mjs +27 -0
  120. package/scripts/lib/async.mjs +174 -0
  121. package/scripts/lib/claudemd-scanner.mjs +223 -0
  122. package/scripts/lib/cli-agy.mjs +62 -0
  123. package/scripts/lib/cli-claude.mjs +78 -0
  124. package/scripts/lib/cli-codex.mjs +199 -0
  125. package/scripts/lib/cli-gemini.mjs +67 -0
  126. package/scripts/lib/codex-recovery.sh +50 -0
  127. package/scripts/lib/context.mjs +67 -0
  128. package/scripts/lib/cross-review-utils.mjs +59 -0
  129. package/scripts/lib/doctor-env-checks.mjs +121 -0
  130. package/scripts/lib/dynamic-route-cli.mjs +107 -0
  131. package/scripts/lib/env-probe.mjs +269 -0
  132. package/scripts/lib/env.mjs +14 -0
  133. package/scripts/lib/gemini-profiles.mjs +119 -0
  134. package/scripts/lib/handoff.mjs +187 -0
  135. package/scripts/lib/hook-utils.mjs +16 -0
  136. package/scripts/lib/hub.mjs +48 -0
  137. package/scripts/lib/keyword-rules.mjs +195 -0
  138. package/scripts/lib/logger.mjs +105 -0
  139. package/scripts/lib/mcp-filter.mjs +883 -0
  140. package/scripts/lib/mcp-gateway-servers.mjs +70 -0
  141. package/scripts/lib/mcp-guard-engine.mjs +2120 -0
  142. package/scripts/lib/mcp-health.mjs +773 -0
  143. package/scripts/lib/mcp-manifest.mjs +89 -0
  144. package/scripts/lib/mcp-server-catalog.mjs +355 -0
  145. package/scripts/lib/pid.mjs +63 -0
  146. package/scripts/lib/process-utils.mjs +26 -0
  147. package/scripts/lib/psmux-info.mjs +179 -0
  148. package/scripts/lib/quota.mjs +47 -0
  149. package/scripts/lib/remote-spawn-transfer.mjs +226 -0
  150. package/scripts/lib/skill-state.mjs +220 -0
  151. package/scripts/lib/skill-template.mjs +375 -0
  152. package/scripts/lib/team.mjs +75 -0
  153. package/scripts/lib/timeout.mjs +66 -0
  154. package/scripts/lib/tmp.mjs +26 -0
  155. package/scripts/lib/toml.mjs +113 -0
@@ -1,8 +1,15 @@
1
- import childProcess from "node:child_process";
2
- import { existsSync, mkdirSync, readFileSync, rmSync } from "node:fs";
1
+ import {
2
+ existsSync,
3
+ mkdirSync,
4
+ readFileSync,
5
+ renameSync,
6
+ rmSync,
7
+ writeFileSync,
8
+ } from "node:fs";
3
9
  import { platform as osPlatform, tmpdir } from "node:os";
4
10
  import { join } from "node:path";
5
-
11
+ import { getEnvironment } from "@triflux/core/hub/lib/env-detect.mjs";
12
+ import * as childProcess from "@triflux/core/hub/lib/spawn-trace.mjs";
6
13
  import { sendKeysToPane } from "./psmux.mjs";
7
14
 
8
15
  const DEFAULT_WINDOW_NAME = "triflux";
@@ -61,8 +68,8 @@ function buildWrappedCommand(pidFile, command) {
61
68
  * CreateProcess 이중 쿼팅 문제를 완전히 회피한다.
62
69
  */
63
70
  function encodeForPowerShell(script) {
64
- const buf = Buffer.from(script, 'utf16le');
65
- return buf.toString('base64');
71
+ const buf = Buffer.from(script, "utf16le");
72
+ return buf.toString("base64");
66
73
  }
67
74
 
68
75
  function matchesTitlePattern(title, pattern) {
@@ -99,6 +106,60 @@ function defaultIsPidAlive(pid, execFileSyncFn = childProcess.execFileSync) {
99
106
  }
100
107
  }
101
108
 
109
+ /**
110
+ * WT 기본 프로필의 폰트 크기를 읽는다.
111
+ */
112
+ function getWtDefaultFontSize() {
113
+ const settingsPaths = [
114
+ join(
115
+ process.env.LOCALAPPDATA || "",
116
+ "Packages/Microsoft.WindowsTerminal_8wekyb3d8bbwe/LocalState/settings.json",
117
+ ),
118
+ join(
119
+ process.env.LOCALAPPDATA || "",
120
+ "Microsoft/Windows Terminal/settings.json",
121
+ ),
122
+ ];
123
+ for (const p of settingsPaths) {
124
+ if (!existsSync(p)) continue;
125
+ try {
126
+ const settings = JSON.parse(
127
+ readFileSync(p, "utf8").replace(/^\s*\/\/.*$/gm, ""),
128
+ );
129
+ const defaultGuid = settings.defaultProfile;
130
+ const profiles = settings.profiles?.list || [];
131
+ const defaultProfile =
132
+ profiles.find((pr) => pr.guid === defaultGuid) || profiles[0];
133
+ return (
134
+ defaultProfile?.font?.size ||
135
+ settings.profiles?.defaults?.font?.size ||
136
+ 12
137
+ );
138
+ } catch {
139
+ /* 다음 */
140
+ }
141
+ }
142
+ return 12;
143
+ }
144
+
145
+ /**
146
+ * 파일을 원자적으로 쓴다.
147
+ */
148
+ function atomicWriteSync(filePath, data) {
149
+ const tmpPath = `${filePath}.${process.pid}.tmp`;
150
+ try {
151
+ writeFileSync(tmpPath, data, "utf8");
152
+ renameSync(tmpPath, filePath);
153
+ } catch (err) {
154
+ try {
155
+ rmSync(tmpPath, { force: true });
156
+ } catch {
157
+ /* 무시 */
158
+ }
159
+ throw err;
160
+ }
161
+ }
162
+
102
163
  /**
103
164
  * Windows Terminal 탭/세션 라이프사이클 관리자.
104
165
  *
@@ -109,12 +170,34 @@ function defaultIsPidAlive(pid, execFileSyncFn = childProcess.execFileSync) {
109
170
  * @param {number} [opts.tabCreateDelayMs=500]
110
171
  * @param {object} [opts.deps] — 테스트용 의존성 주입
111
172
  */
173
+ function createNonWindowsStubManager() {
174
+ const asyncFalse = async () => false;
175
+ return Object.freeze({
176
+ ensureWtProfile: () => {},
177
+ createTab: asyncFalse,
178
+ renameTab: asyncFalse,
179
+ closeTab: asyncFalse,
180
+ listTabs: async () => [],
181
+ closeStale: async () => 0,
182
+ createSession: asyncFalse,
183
+ splitPane: asyncFalse,
184
+ applySplitLayout: asyncFalse,
185
+ getEnvironmentInfo: () => ({
186
+ platform: process.platform,
187
+ hasWindowsTerminal: false,
188
+ hasWt: false,
189
+ isWindowsTerminalSession: false,
190
+ }),
191
+ getTabCount: () => 0,
192
+ });
193
+ }
194
+
112
195
  export function createWtManager(opts = {}) {
113
196
  const deps = opts.deps || {};
114
197
  const platform = deps.platform || osPlatform;
115
198
 
116
199
  if (platform() !== "win32") {
117
- throw new Error("wt-manager.mjs is Windows-only");
200
+ return createNonWindowsStubManager();
118
201
  }
119
202
 
120
203
  const now = deps.now || Date.now;
@@ -123,6 +206,7 @@ export function createWtManager(opts = {}) {
123
206
  const execFileSyncFn = deps.execFileSync || childProcess.execFileSync;
124
207
  const killFn = deps.kill || ((pid) => process.kill(pid));
125
208
  const sendKeysFn = deps.sendKeysToPane || sendKeysToPane;
209
+ const getEnvironmentFn = deps.getEnvironment || getEnvironment;
126
210
  const isPidAlive =
127
211
  deps.isPidAlive || ((pid) => defaultIsPidAlive(pid, execFileSyncFn));
128
212
  const ensureDir =
@@ -130,6 +214,7 @@ export function createWtManager(opts = {}) {
130
214
  const exists = deps.exists || existsSync;
131
215
  const readText =
132
216
  deps.readText || ((filePath) => readFileSync(filePath, "utf8"));
217
+ const renameFile = deps.renameFile || renameSync;
133
218
  const removeFile =
134
219
  deps.removeFile || ((filePath) => rmSync(filePath, { force: true }));
135
220
 
@@ -146,12 +231,19 @@ export function createWtManager(opts = {}) {
146
231
  opts.tabCreateDelayMs,
147
232
  DEFAULT_TAB_CREATE_DELAY_MS,
148
233
  ) || DEFAULT_TAB_CREATE_DELAY_MS;
234
+ const _waitTimeoutMs =
235
+ resolvePositiveInteger(
236
+ opts.waitTimeoutMs,
237
+ process.env.WTM_WAIT_TIMEOUT_MS,
238
+ DEFAULT_WAIT_TIMEOUT_MS,
239
+ ) || DEFAULT_WAIT_TIMEOUT_MS;
149
240
 
150
241
  ensureDir(pidDir);
151
242
 
152
243
  /** @type {Map<string, { pid: number, createdAt: number, pidFile: string }>} */
153
244
  const tabs = new Map();
154
245
  let lastTabCreateAt = null;
246
+ let _profileEnsured = false;
155
247
 
156
248
  function forgetTab(title) {
157
249
  const entry = tabs.get(title);
@@ -200,12 +292,95 @@ export function createWtManager(opts = {}) {
200
292
  throw new Error(`WT tab ready timeout: ${title}`);
201
293
  }
202
294
 
295
+ function ensureWtProfile(workerCount = 2) {
296
+ const settingsPaths = [
297
+ join(
298
+ process.env.LOCALAPPDATA || "",
299
+ "Packages/Microsoft.WindowsTerminal_8wekyb3d8bbwe/LocalState/settings.json",
300
+ ),
301
+ join(
302
+ process.env.LOCALAPPDATA || "",
303
+ "Microsoft/Windows Terminal/settings.json",
304
+ ),
305
+ ];
306
+
307
+ for (const settingsPath of settingsPaths) {
308
+ if (!exists(settingsPath)) continue;
309
+ try {
310
+ const raw = readText(settingsPath);
311
+ const cleaned = raw.replace(/^\s*\/\/.*$/gm, "");
312
+ const settings = JSON.parse(cleaned);
313
+ if (!settings.profiles?.list) continue;
314
+
315
+ const existing = settings.profiles.list.findIndex(
316
+ (p) => p.name === "triflux",
317
+ );
318
+ const profile = {
319
+ name: "triflux",
320
+ commandline: "psmux",
321
+ icon: "\u{1F53A}",
322
+ tabTitle: "triflux",
323
+ suppressApplicationTitle: true,
324
+ opacity: 40,
325
+ useAcrylic: true,
326
+ unfocusedAppearance: { opacity: 20 },
327
+ colorScheme: "One Half Dark",
328
+ font: {
329
+ size: Math.max(
330
+ 6,
331
+ getWtDefaultFontSize() - 1 - Math.floor(workerCount / 2),
332
+ ),
333
+ },
334
+ closeOnExit: "always",
335
+ hidden: true,
336
+ };
337
+
338
+ if (existing >= 0) {
339
+ settings.profiles.list[existing] = {
340
+ ...settings.profiles.list[existing],
341
+ ...profile,
342
+ };
343
+ } else {
344
+ settings.profiles.list.push(profile);
345
+ }
346
+
347
+ atomicWriteSync(settingsPath, JSON.stringify(settings, null, 2));
348
+ _profileEnsured = true;
349
+ return true;
350
+ } catch {
351
+ /* 파싱 실패 */
352
+ }
353
+ }
354
+ return false;
355
+ }
356
+
203
357
  async function createTab(tab = {}) {
204
358
  const title = String(tab.title || "").trim();
205
359
  if (!title) {
206
360
  throw new Error("title is required");
207
361
  }
208
362
 
363
+ const env = getEnvironmentFn();
364
+ if (env?.terminal?.hasWt === false) {
365
+ return Object.freeze({
366
+ success: false,
367
+ reason: "wt-not-installed",
368
+ installHint: env.terminal.installHint || null,
369
+ });
370
+ }
371
+
372
+ const shellPath = String(opts.profile || env?.shell?.path || "pwsh.exe");
373
+
374
+ // 프로필이 지정된 경우 WT settings.json에 존재하는지 보장
375
+ // headless가 workerCount 기반으로 이미 호출한 경우 _profileEnsured로 skip
376
+ if (tab.profile && !_profileEnsured) {
377
+ try {
378
+ ensureWtProfile();
379
+ } catch {
380
+ /* 프로필 보장 실패해도 진행 */
381
+ }
382
+ }
383
+
209
384
  pruneDeadTabs();
210
385
 
211
386
  if (tabs.has(title)) {
@@ -235,7 +410,7 @@ export function createWtManager(opts = {}) {
235
410
  const script = buildWrappedCommand(pidFile, tab.command);
236
411
  args.push(
237
412
  "--",
238
- "powershell.exe",
413
+ shellPath,
239
414
  "-NoExit",
240
415
  "-EncodedCommand",
241
416
  encodeForPowerShell(script),
@@ -255,6 +430,69 @@ export function createWtManager(opts = {}) {
255
430
  pidFile,
256
431
  });
257
432
  tabs.set(title, entry);
433
+ return Object.freeze({
434
+ success: true,
435
+ title,
436
+ pid: entry.pid,
437
+ createdAt: entry.createdAt,
438
+ pidFile: entry.pidFile,
439
+ });
440
+ }
441
+
442
+ function renameTab({ oldTitle, newTitle } = {}) {
443
+ const previousTitle = String(oldTitle || "").trim();
444
+ const nextTitle = String(newTitle || "").trim();
445
+ if (!previousTitle) {
446
+ throw new Error("oldTitle is required");
447
+ }
448
+ if (!nextTitle) {
449
+ throw new Error("newTitle is required");
450
+ }
451
+
452
+ pruneDeadTabs();
453
+
454
+ const entry = tabs.get(previousTitle);
455
+ if (!entry) {
456
+ return false;
457
+ }
458
+ if (previousTitle === nextTitle) {
459
+ return Object.freeze({
460
+ success: true,
461
+ title: nextTitle,
462
+ pid: entry.pid,
463
+ createdAt: entry.createdAt,
464
+ pidFile: entry.pidFile,
465
+ });
466
+ }
467
+ if (tabs.has(nextTitle)) {
468
+ throw new Error(`WT tab already exists: ${nextTitle}`);
469
+ }
470
+
471
+ const nextPidFile = buildPidFilePath(pidDir, nextTitle);
472
+ try {
473
+ removeFile(nextPidFile);
474
+ } catch {
475
+ /* ignore */
476
+ }
477
+ renameFile(entry.pidFile, nextPidFile);
478
+
479
+ tabs.delete(previousTitle);
480
+ const nextEntry = Object.freeze({
481
+ ...entry,
482
+ pidFile: nextPidFile,
483
+ });
484
+ tabs.set(nextTitle, nextEntry);
485
+ return Object.freeze({
486
+ success: true,
487
+ title: nextTitle,
488
+ pid: nextEntry.pid,
489
+ createdAt: nextEntry.createdAt,
490
+ pidFile: nextEntry.pidFile,
491
+ });
492
+ }
493
+
494
+ function getEnvironmentInfo() {
495
+ return getEnvironmentFn();
258
496
  }
259
497
 
260
498
  async function closeTab(title) {
@@ -315,30 +553,47 @@ export function createWtManager(opts = {}) {
315
553
  * @param {string} [opts.command] — pane에서 실행할 명령
316
554
  * @param {number} [opts.size] — 퍼센트 (0-100)
317
555
  */
318
- async function splitPane(opts = {}) {
319
- const direction = opts.direction === 'H' ? '-H' : '-V';
320
- const args = ['-w', windowName, 'sp', direction];
556
+ async function splitPane(splitOpts = {}) {
557
+ // 프로필이 지정된 경우 WT settings.json에 존재하는지 보장
558
+ if (splitOpts.profile && !_profileEnsured) {
559
+ try {
560
+ ensureWtProfile();
561
+ } catch {
562
+ /* 프로필 보장 실패해도 진행 */
563
+ }
564
+ }
565
+
566
+ const direction = splitOpts.direction === "H" ? "-H" : "-V";
567
+ const args = ["-w", windowName, "sp", direction];
321
568
 
322
- if (opts.title) {
323
- args.push('--title', String(opts.title));
569
+ if (splitOpts.title) {
570
+ args.push("--title", String(splitOpts.title));
324
571
  }
325
- if (opts.profile) {
326
- args.push('--profile', String(opts.profile));
572
+ if (splitOpts.profile) {
573
+ args.push("--profile", String(splitOpts.profile));
327
574
  }
328
- if (opts.size && Number.isFinite(opts.size)) {
329
- args.push('-s', String(opts.size / 100));
575
+ if (splitOpts.size && Number.isFinite(splitOpts.size)) {
576
+ args.push("-s", String(splitOpts.size / 100));
330
577
  }
331
- if (opts.cwd) {
332
- args.push('-d', String(opts.cwd));
578
+ if (splitOpts.cwd) {
579
+ args.push("-d", String(splitOpts.cwd));
333
580
  }
334
- if (opts.command) {
335
- const script = opts.command;
336
- args.push('--', 'powershell.exe', '-NoExit', '-EncodedCommand', encodeForPowerShell(script));
581
+ if (splitOpts.command) {
582
+ const env = getEnvironmentFn();
583
+ const shellPath = String(opts.profile || env?.shell?.path || "pwsh.exe");
584
+ const script = splitOpts.command;
585
+ args.push(
586
+ "--",
587
+ shellPath,
588
+ "-NoExit",
589
+ "-EncodedCommand",
590
+ encodeForPowerShell(script),
591
+ );
337
592
  }
338
593
 
339
- const child = spawnFn('wt.exe', args, {
594
+ const child = spawnFn("wt.exe", args, {
340
595
  detached: true,
341
- stdio: 'ignore',
596
+ stdio: "ignore",
342
597
  });
343
598
  child?.unref?.();
344
599
 
@@ -355,18 +610,27 @@ export function createWtManager(opts = {}) {
355
610
 
356
611
  // 첫 번째는 새 탭으로 생성
357
612
  const first = panes[0];
358
- await createTab({ title: first.title, command: first.command, profile: first.profile });
613
+ const createResult = await createTab({
614
+ title: first.title,
615
+ command: first.command,
616
+ profile: first.profile,
617
+ });
618
+ if (createResult?.success === false) {
619
+ return createResult;
620
+ }
359
621
 
360
622
  // 나머지는 split-pane으로 분할
361
623
  for (let i = 1; i < panes.length; i++) {
362
624
  await splitPane({
363
- direction: panes[i].direction || 'V',
625
+ direction: panes[i].direction || "V",
364
626
  title: panes[i].title,
365
627
  command: panes[i].command,
366
628
  profile: panes[i].profile,
367
629
  size: panes[i].size,
368
630
  });
369
631
  }
632
+
633
+ return createResult;
370
634
  }
371
635
 
372
636
  async function createSession(sessionOpts = {}) {
@@ -385,8 +649,12 @@ export function createWtManager(opts = {}) {
385
649
  throw new Error("command is required");
386
650
  }
387
651
 
388
- await createTab(tab);
652
+ const createResult = await createTab(tab);
653
+ if (createResult?.success === false) {
654
+ return createResult;
655
+ }
389
656
  sendKeysFn(String(sessionOpts.pane), String(sessionOpts.command), true);
657
+ return createResult;
390
658
  }
391
659
 
392
660
  function getTabCount() {
@@ -395,13 +663,16 @@ export function createWtManager(opts = {}) {
395
663
  }
396
664
 
397
665
  return Object.freeze({
666
+ ensureWtProfile,
398
667
  createTab,
668
+ renameTab,
399
669
  closeTab,
400
670
  listTabs,
401
671
  closeStale,
402
672
  createSession,
403
673
  splitPane,
404
674
  applySplitLayout,
675
+ getEnvironmentInfo,
405
676
  getTabCount,
406
677
  });
407
678
  }