triflux 10.3.4 → 10.7.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.
- package/LICENSE +21 -21
- package/bin/tfx-doctor-tui.mjs +1 -1
- package/bin/tfx-doctor.mjs +6 -1
- package/bin/tfx-profile.mjs +1 -1
- package/bin/tfx-setup-tui.mjs +1 -1
- package/bin/tfx-setup.mjs +6 -1
- package/bin/triflux.mjs +2396 -1140
- package/hooks/agent-route-guard.mjs +12 -8
- package/hooks/cross-review-tracker.mjs +21 -8
- package/hooks/error-context.mjs +19 -7
- package/hooks/hook-adaptive-collector.mjs +18 -16
- package/hooks/hook-manager.mjs +93 -32
- package/hooks/hook-orchestrator.mjs +108 -24
- package/hooks/hook-registry.json +11 -0
- package/hooks/keyword-rules.json +6 -10
- package/hooks/lib/resolve-root.mjs +1 -1
- package/hooks/mcp-config-watcher.mjs +6 -2
- package/hooks/pipeline-stop.mjs +3 -6
- package/hooks/safety-guard.mjs +99 -28
- package/hooks/session-start-fast.mjs +143 -0
- package/hooks/subagent-verifier.mjs +5 -4
- package/hub/account-broker.mjs +256 -60
- package/hub/adaptive-diagnostic.mjs +75 -48
- package/hub/adaptive-inject.mjs +95 -57
- package/hub/adaptive-memory.mjs +156 -42
- package/hub/adaptive.mjs +60 -31
- package/hub/assign-callbacks.mjs +67 -30
- package/hub/bridge.mjs +0 -1
- package/hub/cli-adapter-base.mjs +200 -48
- package/hub/codex-adapter.mjs +76 -96
- package/hub/codex-compat.mjs +3 -3
- package/hub/codex-preflight.mjs +63 -37
- package/hub/delegator/contracts.mjs +19 -23
- package/hub/delegator/index.mjs +3 -3
- package/hub/delegator/service.mjs +88 -64
- package/hub/delegator/tool-definitions.mjs +5 -5
- package/hub/fullcycle.mjs +33 -17
- package/hub/gemini-adapter.mjs +69 -94
- package/hub/hitl.mjs +89 -30
- package/hub/intent.mjs +161 -38
- package/hub/lib/cache-guard.mjs +43 -17
- package/hub/lib/mcp-response-cache.mjs +66 -32
- package/hub/lib/memory-store.mjs +285 -111
- package/hub/lib/path-utils.mjs +35 -37
- package/hub/lib/process-utils.mjs +106 -37
- package/hub/lib/spawn-trace.mjs +527 -0
- package/hub/lib/ssh-command.mjs +34 -4
- package/hub/lib/ssh-retry.mjs +5 -1
- package/hub/lib/uuidv7.mjs +4 -3
- package/hub/memory-doctor.mjs +266 -106
- package/hub/middleware/request-logger.mjs +61 -34
- package/hub/paths.mjs +9 -9
- package/hub/pipeline/gates/confidence.mjs +34 -15
- package/hub/pipeline/gates/consensus.mjs +27 -15
- package/hub/pipeline/gates/index.mjs +7 -3
- package/hub/pipeline/gates/selfcheck.mjs +57 -19
- package/hub/pipeline/index.mjs +77 -42
- package/hub/pipeline/state.mjs +10 -10
- package/hub/pipeline/transitions.mjs +40 -23
- package/hub/platform.mjs +57 -48
- package/hub/promote-penalties.mjs +25 -7
- package/hub/quality/deslop.mjs +70 -49
- package/hub/research.mjs +32 -25
- package/hub/router.mjs +240 -107
- package/hub/routing/complexity.mjs +132 -29
- package/hub/routing/index.mjs +17 -12
- package/hub/routing/q-learning.mjs +76 -28
- package/hub/server.mjs +4 -4
- package/hub/session-fingerprint.mjs +126 -60
- package/hub/state.mjs +84 -43
- package/hub/store-adapter.mjs +59 -26
- package/hub/store.mjs +356 -153
- package/hub/team/agent-map.json +22 -7
- package/hub/team/ansi.mjs +186 -122
- package/hub/team/backend.mjs +28 -10
- package/hub/team/cli/commands/attach.mjs +29 -9
- package/hub/team/cli/commands/control.mjs +29 -8
- package/hub/team/cli/commands/debug.mjs +32 -11
- package/hub/team/cli/commands/focus.mjs +38 -11
- package/hub/team/cli/commands/interrupt.mjs +18 -6
- package/hub/team/cli/commands/kill.mjs +16 -5
- package/hub/team/cli/commands/list.mjs +11 -4
- package/hub/team/cli/commands/send.mjs +19 -6
- package/hub/team/cli/commands/start/index.mjs +154 -31
- package/hub/team/cli/commands/start/parse-args.mjs +38 -11
- package/hub/team/cli/commands/start/start-headless.mjs +112 -36
- package/hub/team/cli/commands/start/start-in-process.mjs +12 -2
- package/hub/team/cli/commands/start/start-mux.mjs +70 -21
- package/hub/team/cli/commands/start/start-wt.mjs +29 -12
- package/hub/team/cli/commands/status.mjs +43 -14
- package/hub/team/cli/commands/stop.mjs +11 -4
- package/hub/team/cli/commands/task.mjs +8 -3
- package/hub/team/cli/commands/tasks.mjs +1 -1
- package/hub/team/cli/index.mjs +2 -2
- package/hub/team/cli/manifest.mjs +38 -8
- package/hub/team/cli/render.mjs +30 -8
- package/hub/team/cli/services/attach-fallback.mjs +31 -11
- package/hub/team/cli/services/hub-client.mjs +42 -14
- package/hub/team/cli/services/member-selector.mjs +11 -4
- package/hub/team/cli/services/native-control.mjs +48 -21
- package/hub/team/cli/services/runtime-mode.mjs +2 -1
- package/hub/team/cli/services/state-store.mjs +25 -8
- package/hub/team/cli/services/task-model.mjs +16 -6
- package/hub/team/conductor-mesh-bridge.mjs +24 -23
- package/hub/team/conductor.mjs +8 -4
- package/hub/team/dashboard-anchor.mjs +4 -5
- package/hub/team/dashboard-layout.mjs +3 -1
- package/hub/team/dashboard-open.mjs +41 -21
- package/hub/team/dashboard.mjs +76 -28
- package/hub/team/event-log.mjs +18 -10
- package/hub/team/handoff.mjs +31 -15
- package/hub/team/headless.mjs +2 -1
- package/hub/team/health-probe.mjs +69 -54
- package/hub/team/launcher-template.mjs +16 -13
- package/hub/team/native-supervisor.mjs +65 -21
- package/hub/team/native.mjs +74 -35
- package/hub/team/nativeProxy.mjs +184 -113
- package/hub/team/notify.mjs +119 -76
- package/hub/team/orchestrator.mjs +9 -4
- package/hub/team/pane.mjs +12 -7
- package/hub/team/process-cleanup.mjs +25 -16
- package/hub/team/psmux.mjs +491 -201
- package/hub/team/remote-probe.mjs +68 -52
- package/hub/team/remote-session.mjs +117 -59
- package/hub/team/remote-watcher.mjs +61 -33
- package/hub/team/routing.mjs +51 -25
- package/hub/team/runtime-strategy.mjs +3 -1
- package/hub/team/session.mjs +98 -34
- package/hub/team/staleState.mjs +72 -30
- package/hub/team/swarm-locks.mjs +15 -13
- package/hub/team/swarm-planner.mjs +32 -21
- package/hub/team/swarm-reconciler.mjs +48 -23
- package/hub/team/tui-lite.mjs +266 -68
- package/hub/team/tui-remote-adapter.mjs +14 -10
- package/hub/team/tui-viewer.mjs +99 -43
- package/hub/team/tui.mjs +708 -271
- package/hub/team/worktree-lifecycle.mjs +152 -58
- package/hub/team/wt-manager.mjs +24 -14
- package/hub/token-mode.mjs +71 -71
- package/hub/tray.mjs +66 -23
- package/hub/workers/claude-worker.mjs +162 -118
- package/hub/workers/codex-mcp.mjs +192 -141
- package/hub/workers/delegator-mcp.mjs +507 -333
- package/hub/workers/factory.mjs +8 -8
- package/hub/workers/gemini-worker.mjs +115 -84
- package/hub/workers/interface.mjs +6 -1
- package/hub/workers/worker-utils.mjs +21 -14
- package/hud/colors.mjs +27 -9
- package/hud/constants.mjs +162 -26
- package/hud/context-monitor.mjs +82 -41
- package/hud/hud-qos-status.mjs +129 -49
- package/hud/mission-board.mjs +6 -3
- package/hud/providers/claude.mjs +226 -115
- package/hud/providers/codex.mjs +62 -22
- package/hud/providers/gemini.mjs +168 -56
- package/hud/renderers.mjs +384 -119
- package/hud/terminal.mjs +101 -31
- package/hud/utils.mjs +78 -38
- package/mesh/index.mjs +11 -5
- package/mesh/mesh-budget.mjs +18 -9
- package/mesh/mesh-heartbeat.mjs +1 -1
- package/mesh/mesh-queue.mjs +3 -5
- package/mesh/mesh-router.mjs +5 -4
- package/package.json +2 -1
- package/scripts/__tests__/gen-skill-docs.test.mjs +36 -7
- package/scripts/__tests__/keyword-detector.test.mjs +77 -28
- package/scripts/__tests__/mcp-guard-engine.test.mjs +58 -20
- package/scripts/__tests__/remote-spawn-transfer.test.mjs +30 -19
- package/scripts/__tests__/remote-spawn.test.mjs +10 -4
- package/scripts/__tests__/session-start-fast.test.mjs +36 -0
- package/scripts/__tests__/skill-template.test.mjs +98 -50
- package/scripts/__tests__/smoke.test.mjs +1 -1
- package/scripts/__tests__/spawn-trace.test.mjs +102 -0
- package/scripts/__tests__/tfx-doctor-diagnose.test.mjs +48 -0
- package/scripts/cache-doctor.mjs +11 -4
- package/scripts/cache-warmup.mjs +96 -37
- package/scripts/claudemd-sync.mjs +27 -17
- package/scripts/codex-gateway-preflight.mjs +52 -37
- package/scripts/codex-mcp-gateway-sync.mjs +59 -39
- package/scripts/completions/tfx.bash +47 -47
- package/scripts/completions/tfx.fish +44 -44
- package/scripts/completions/tfx.zsh +83 -83
- package/scripts/config-audit.mjs +232 -0
- package/scripts/convert-to-tmpl.mjs +54 -0
- package/scripts/cross-review-gate.mjs +35 -12
- package/scripts/cross-review-tracker.mjs +21 -8
- package/scripts/demo.mjs +35 -17
- package/scripts/doctor-diagnose.mjs +284 -0
- package/scripts/gen-skill-docs.mjs +7 -2
- package/scripts/gen-skill-manifest.mjs +2 -1
- package/scripts/headless-guard.mjs +86 -48
- package/scripts/hub-ensure.mjs +45 -26
- package/scripts/keyword-detector.mjs +41 -20
- package/scripts/keyword-rules-expander.mjs +47 -30
- package/scripts/lib/claudemd-scanner.mjs +6 -1
- package/scripts/lib/context.mjs +3 -3
- package/scripts/lib/cross-review-utils.mjs +6 -3
- package/scripts/lib/env-probe.mjs +47 -28
- package/scripts/lib/gemini-profiles.mjs +44 -10
- package/scripts/lib/handoff.mjs +33 -17
- package/scripts/lib/hook-utils.mjs +8 -6
- package/scripts/lib/keyword-rules.mjs +43 -19
- package/scripts/lib/logger.mjs +24 -24
- package/scripts/lib/mcp-filter.mjs +377 -239
- package/scripts/lib/mcp-guard-engine.mjs +194 -79
- package/scripts/lib/mcp-manifest.mjs +23 -13
- package/scripts/lib/mcp-server-catalog.mjs +300 -63
- package/scripts/lib/psmux-info.mjs +11 -6
- package/scripts/lib/remote-spawn-transfer.mjs +44 -14
- package/scripts/lib/skill-template.mjs +30 -7
- package/scripts/mcp-check.mjs +58 -39
- package/scripts/mcp-gateway-config.mjs +83 -39
- package/scripts/mcp-gateway-ensure.mjs +43 -35
- package/scripts/mcp-gateway-integration-test.mjs +70 -58
- package/scripts/mcp-gateway-start.mjs +126 -60
- package/scripts/mcp-gateway-verify.mjs +24 -22
- package/scripts/mcp-safety-guard.mjs +44 -11
- package/scripts/notion-read.mjs +199 -84
- package/scripts/pack.mjs +94 -89
- package/scripts/preflight-cache.mjs +27 -10
- package/scripts/preinstall.mjs +42 -13
- package/scripts/remote-spawn.mjs +309 -94
- package/scripts/run.cjs +8 -5
- package/scripts/session-spawn-helper.mjs +130 -39
- package/scripts/session-stale-cleanup.mjs +123 -0
- package/scripts/setup.mjs +941 -492
- package/scripts/test-lock.mjs +20 -7
- package/scripts/test-tfx-route-no-claude-native.mjs +16 -12
- package/scripts/tfx-batch-stats.mjs +32 -11
- package/scripts/tfx-gate-activate.mjs +11 -4
- package/scripts/tfx-route-post.mjs +87 -20
- package/scripts/tfx-route-worker.mjs +57 -51
- package/scripts/tfx-route.sh +41 -124
- package/scripts/tmp-cleanup.mjs +21 -7
- package/scripts/token-snapshot.mjs +204 -85
- package/skills/.omc/state/agent-replay-8f0e10a9-9693-4410-96f5-a6b07e8ed995.jsonl +1 -0
- package/skills/.omc/state/idle-notif-cooldown.json +3 -0
- package/skills/.omc/state/last-tool-error.json +7 -0
- package/skills/.omc/state/subagent-tracking.json +7 -0
- package/skills/_templates/base.md +1 -6
- package/skills/merge-worktree/SKILL.md.tmpl +144 -0
- package/skills/shared/telemetry-segment.md +6 -0
- package/skills/star-prompt/SKILL.md.tmpl +222 -0
- package/skills/tfx-analysis/SKILL.md.tmpl +107 -0
- package/skills/tfx-analysis/skill.json +1 -6
- package/skills/tfx-auto/SKILL.md +1 -0
- package/skills/tfx-auto-codex/SKILL.md.tmpl +106 -0
- package/skills/tfx-auto-codex/skill.json +1 -3
- package/skills/tfx-autopilot/SKILL.md.tmpl +116 -0
- package/skills/tfx-autopilot/skill.json +1 -5
- package/skills/tfx-autoresearch/SKILL.md.tmpl +136 -0
- package/skills/tfx-autoroute/SKILL.md.tmpl +189 -0
- package/skills/tfx-autoroute/skill.json +1 -7
- package/skills/tfx-codex/SKILL.md +1 -0
- package/skills/tfx-codex/skill.json +1 -3
- package/skills/tfx-codex-swarm/SKILL.md.tmpl +16 -0
- package/skills/tfx-codex-swarm/evals/evals.json +1 -1
- package/skills/tfx-codex-swarm/skill.json +1 -4
- package/skills/tfx-codex-swarm-workspace/iteration-1/benchmark.json +54 -12
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/grading.json +35 -7
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/grading.json +35 -7
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/grading.json +25 -5
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/grading.json +25 -5
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/grading.json +20 -4
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/grading.json +16 -4
- package/skills/tfx-consensus/SKILL.md.tmpl +146 -0
- package/skills/tfx-debate/SKILL.md.tmpl +192 -0
- package/skills/tfx-debate/skill.json +1 -7
- package/skills/tfx-deep-analysis/SKILL.md.tmpl +228 -0
- package/skills/tfx-deep-analysis/skill.json +1 -5
- package/skills/tfx-deep-interview/SKILL.md.tmpl +203 -0
- package/skills/tfx-deep-plan/SKILL.md.tmpl +282 -0
- package/skills/tfx-deep-qa/SKILL.md.tmpl +165 -0
- package/skills/tfx-deep-qa/skill.json +1 -6
- package/skills/tfx-deep-research/SKILL.md.tmpl +217 -0
- package/skills/tfx-deep-review/SKILL.md.tmpl +179 -0
- package/skills/tfx-doctor/SKILL.md +21 -0
- package/skills/tfx-doctor/SKILL.md.tmpl +172 -0
- package/skills/tfx-doctor/skill.json +1 -3
- package/skills/tfx-find/SKILL.md +1 -0
- package/skills/tfx-forge/SKILL.md.tmpl +187 -0
- package/skills/tfx-fullcycle/SKILL.md.tmpl +286 -0
- package/skills/tfx-fullcycle/skill.json +1 -6
- package/skills/tfx-gemini/SKILL.md.tmpl +91 -0
- package/skills/tfx-gemini/skill.json +1 -3
- package/skills/tfx-hooks/SKILL.md.tmpl +216 -0
- package/skills/tfx-hooks/skill.json +1 -3
- package/skills/tfx-hub/SKILL.md.tmpl +212 -0
- package/skills/tfx-hub/skill.json +1 -3
- package/skills/tfx-index/SKILL.md +1 -0
- package/skills/tfx-index/skill.json +1 -6
- package/skills/tfx-interview/SKILL.md.tmpl +285 -0
- package/skills/tfx-multi/SKILL.md.tmpl +183 -0
- package/skills/tfx-multi/skill.json +1 -3
- package/skills/tfx-panel/SKILL.md.tmpl +189 -0
- package/skills/tfx-panel/skill.json +1 -7
- package/skills/tfx-persist/SKILL.md.tmpl +270 -0
- package/skills/tfx-persist/skill.json +1 -7
- package/skills/tfx-plan/SKILL.md +1 -0
- package/skills/tfx-plan/skill.json +1 -6
- package/skills/tfx-profile/SKILL.md.tmpl +239 -0
- package/skills/tfx-profile/skill.json +1 -3
- package/skills/tfx-prune/SKILL.md.tmpl +200 -0
- package/skills/tfx-prune/skill.json +1 -7
- package/skills/tfx-psmux-rules/SKILL.md.tmpl +326 -0
- package/skills/tfx-psmux-rules/skill.json +1 -4
- package/skills/tfx-qa/SKILL.md +1 -0
- package/skills/tfx-qa/skill.json +1 -6
- package/skills/tfx-ralph/SKILL.md.tmpl +28 -0
- package/skills/tfx-ralph/skill.json +1 -4
- package/skills/tfx-remote-setup/SKILL.md.tmpl +576 -0
- package/skills/tfx-remote-setup/skill.json +1 -3
- package/skills/tfx-remote-spawn/SKILL.md.tmpl +263 -0
- package/skills/tfx-remote-spawn/references/hosts.json +16 -0
- package/skills/tfx-remote-spawn/skill.json +1 -4
- package/skills/tfx-research/SKILL.md +1 -0
- package/skills/tfx-review/SKILL.md +1 -0
- package/skills/tfx-review/skill.json +1 -6
- package/skills/tfx-setup/SKILL.md.tmpl +504 -0
- package/skills/tfx-setup/skill.json +1 -3
- package/skills/tfx-swarm/SKILL.md +22 -0
- package/skills/tfx-swarm/SKILL.md.tmpl +218 -0
- package/tui/codex-profile.mjs +88 -33
- package/tui/core.mjs +45 -15
- package/tui/doctor.mjs +75 -28
- package/tui/gemini-profile.mjs +74 -29
- package/tui/monitor-data.mjs +8 -4
- package/tui/monitor.mjs +71 -27
- package/tui/setup.mjs +133 -42
package/hub/team/psmux.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// hub/team/psmux.mjs — Windows psmux 세션/키바인딩/캡처/steering 관리
|
|
2
2
|
// 의존성: child_process, fs, os, path (Node.js 내장)만 사용
|
|
3
|
-
import childProcess from "
|
|
3
|
+
import * as childProcess from "../lib/spawn-trace.mjs";
|
|
4
4
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
|
-
import {
|
|
5
|
+
import { homedir, tmpdir } from "node:os";
|
|
6
6
|
import { join } from "node:path";
|
|
7
7
|
import { formatPsmuxInstallGuidance } from "../../scripts/lib/psmux-info.mjs";
|
|
8
8
|
import { IS_WINDOWS } from "../platform.mjs";
|
|
@@ -11,9 +11,15 @@ const PSMUX_BIN = (() => {
|
|
|
11
11
|
if (process.env.PSMUX_BIN) return process.env.PSMUX_BIN;
|
|
12
12
|
// PATH에서 찾기
|
|
13
13
|
try {
|
|
14
|
-
childProcess.execFileSync("psmux", ["-V"], {
|
|
14
|
+
childProcess.execFileSync("psmux", ["-V"], {
|
|
15
|
+
stdio: "ignore",
|
|
16
|
+
timeout: 2000,
|
|
17
|
+
windowsHide: true,
|
|
18
|
+
});
|
|
15
19
|
return "psmux";
|
|
16
|
-
} catch {
|
|
20
|
+
} catch {
|
|
21
|
+
/* not in PATH */
|
|
22
|
+
}
|
|
17
23
|
// Windows 기본 설치 경로 탐색
|
|
18
24
|
if (IS_WINDOWS) {
|
|
19
25
|
const candidates = [
|
|
@@ -28,7 +34,8 @@ const PSMUX_BIN = (() => {
|
|
|
28
34
|
}
|
|
29
35
|
return "psmux"; // 최종 fallback — 원래대로
|
|
30
36
|
})();
|
|
31
|
-
const GIT_BASH =
|
|
37
|
+
const GIT_BASH =
|
|
38
|
+
process.env.GIT_BASH_PATH || "C:\\Program Files\\Git\\bin\\bash.exe";
|
|
32
39
|
|
|
33
40
|
/** Windows psmux 세션의 기본 셸을 PowerShell로 강제한다 (pwsh7 우선, ps5 fallback). */
|
|
34
41
|
const PWSH_BIN = (() => {
|
|
@@ -36,25 +43,40 @@ const PWSH_BIN = (() => {
|
|
|
36
43
|
if (process.env.PSMUX_SHELL) return process.env.PSMUX_SHELL;
|
|
37
44
|
// pwsh 7 우선
|
|
38
45
|
try {
|
|
39
|
-
childProcess.execFileSync(
|
|
46
|
+
childProcess.execFileSync(
|
|
47
|
+
"pwsh",
|
|
48
|
+
["-NoLogo", "-NoProfile", "-Command", "exit 0"],
|
|
49
|
+
{ stdio: "ignore", timeout: 3000, windowsHide: true },
|
|
50
|
+
);
|
|
40
51
|
return "pwsh";
|
|
41
|
-
} catch {
|
|
52
|
+
} catch {
|
|
53
|
+
/* not found */
|
|
54
|
+
}
|
|
42
55
|
// powershell 5 fallback
|
|
43
56
|
try {
|
|
44
|
-
childProcess.execFileSync(
|
|
57
|
+
childProcess.execFileSync(
|
|
58
|
+
"powershell.exe",
|
|
59
|
+
["-NoLogo", "-NoProfile", "-Command", "exit 0"],
|
|
60
|
+
{ stdio: "ignore", timeout: 3000, windowsHide: true },
|
|
61
|
+
);
|
|
45
62
|
return "powershell.exe";
|
|
46
|
-
} catch {
|
|
63
|
+
} catch {
|
|
64
|
+
/* not found */
|
|
65
|
+
}
|
|
47
66
|
return ""; // 둘 다 없으면 psmux 기본 셸 사용
|
|
48
67
|
})();
|
|
49
68
|
const PSMUX_TIMEOUT_MS = 10000;
|
|
50
69
|
const COMPLETION_PREFIX = "__TRIFLUX_DONE__:";
|
|
51
|
-
const CAPTURE_ROOT =
|
|
70
|
+
const CAPTURE_ROOT =
|
|
71
|
+
process.env.PSMUX_CAPTURE_ROOT || join(tmpdir(), "psmux-steering");
|
|
52
72
|
const CAPTURE_HELPER_PATH = join(CAPTURE_ROOT, "pipe-pane-capture.ps1");
|
|
53
73
|
const POLL_INTERVAL_MS = (() => {
|
|
54
74
|
const ms = Number.parseInt(process.env.PSMUX_POLL_INTERVAL_MS || "", 10);
|
|
55
75
|
if (Number.isFinite(ms) && ms > 0) return ms;
|
|
56
76
|
const sec = Number.parseFloat(process.env.PSMUX_POLL_INTERVAL_SEC || "1");
|
|
57
|
-
return Number.isFinite(sec) && sec > 0
|
|
77
|
+
return Number.isFinite(sec) && sec > 0
|
|
78
|
+
? Math.max(100, Math.trunc(sec * 1000))
|
|
79
|
+
: 1000;
|
|
58
80
|
})();
|
|
59
81
|
|
|
60
82
|
function quoteArg(value) {
|
|
@@ -126,7 +148,7 @@ function tokenizeCommand(command) {
|
|
|
126
148
|
continue;
|
|
127
149
|
}
|
|
128
150
|
|
|
129
|
-
if (char === "\\" && next &&
|
|
151
|
+
if (char === "\\" && next && /[\s"'\\;]/u.test(next)) {
|
|
130
152
|
current += next;
|
|
131
153
|
index += 1;
|
|
132
154
|
continue;
|
|
@@ -165,10 +187,10 @@ function ensurePsmuxInstalled() {
|
|
|
165
187
|
if (!hasPsmux()) {
|
|
166
188
|
throw new Error(
|
|
167
189
|
"psmux가 설치되어 있지 않습니다.\n\n" +
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
190
|
+
"psmux는 Codex/Gemini CLI를 병렬 세션으로 실행하는 터미널 멀티플렉서입니다.\n" +
|
|
191
|
+
"설치 방법 (택 1):\n" +
|
|
192
|
+
`${formatPsmuxInstallGuidance(" ")}\n\n` +
|
|
193
|
+
"설치 후 터미널을 재시작하세요.",
|
|
172
194
|
);
|
|
173
195
|
}
|
|
174
196
|
}
|
|
@@ -178,7 +200,10 @@ function getCaptureSessionDir(sessionName) {
|
|
|
178
200
|
}
|
|
179
201
|
|
|
180
202
|
function getCaptureLogPath(sessionName, paneName) {
|
|
181
|
-
return join(
|
|
203
|
+
return join(
|
|
204
|
+
getCaptureSessionDir(sessionName),
|
|
205
|
+
`${sanitizePathPart(paneName)}.log`,
|
|
206
|
+
);
|
|
182
207
|
}
|
|
183
208
|
|
|
184
209
|
function ensureCaptureHelper() {
|
|
@@ -247,7 +272,9 @@ function parseSessionSummaries(output) {
|
|
|
247
272
|
}
|
|
248
273
|
|
|
249
274
|
const sessionName = line.slice(0, colonIndex).trim();
|
|
250
|
-
const flags = [...line.matchAll(/\(([^)]*)\)/g)]
|
|
275
|
+
const flags = [...line.matchAll(/\(([^)]*)\)/g)]
|
|
276
|
+
.map((match) => match[1])
|
|
277
|
+
.join(", ");
|
|
251
278
|
const attachedMatch = flags.match(/(\d+)\s+attached/);
|
|
252
279
|
const attachedCount = attachedMatch
|
|
253
280
|
? parseInt(attachedMatch[1], 10)
|
|
@@ -255,9 +282,7 @@ function parseSessionSummaries(output) {
|
|
|
255
282
|
? 1
|
|
256
283
|
: 0;
|
|
257
284
|
|
|
258
|
-
return sessionName
|
|
259
|
-
? { sessionName, attachedCount }
|
|
260
|
-
: null;
|
|
285
|
+
return sessionName ? { sessionName, attachedCount } : null;
|
|
261
286
|
})
|
|
262
287
|
.filter(Boolean);
|
|
263
288
|
}
|
|
@@ -270,19 +295,25 @@ function parsePaneDetails(output) {
|
|
|
270
295
|
.map((line) => {
|
|
271
296
|
const parts = line.split("\t");
|
|
272
297
|
const hasPaneIndex = parts.length >= 5;
|
|
273
|
-
const [
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
298
|
+
const [
|
|
299
|
+
paneIndexText = "",
|
|
300
|
+
title = "",
|
|
301
|
+
paneId = "",
|
|
302
|
+
dead = "0",
|
|
303
|
+
deadStatus = "",
|
|
304
|
+
] = hasPaneIndex ? parts : ["", ...parts];
|
|
305
|
+
const exitCode = dead === "1" ? Number.parseInt(deadStatus, 10) : null;
|
|
279
306
|
const paneIndex = Number.parseInt(paneIndexText, 10);
|
|
280
307
|
return {
|
|
281
308
|
title,
|
|
282
309
|
paneId,
|
|
283
310
|
paneIndex: Number.isFinite(paneIndex) ? paneIndex : null,
|
|
284
311
|
isDead: dead === "1",
|
|
285
|
-
exitCode: Number.isFinite(exitCode)
|
|
312
|
+
exitCode: Number.isFinite(exitCode)
|
|
313
|
+
? exitCode
|
|
314
|
+
: dead === "1"
|
|
315
|
+
? 0
|
|
316
|
+
: null,
|
|
286
317
|
};
|
|
287
318
|
})
|
|
288
319
|
.filter((entry) => entry.paneId);
|
|
@@ -325,7 +356,9 @@ function resolvePane(sessionName, paneNameOrTarget) {
|
|
|
325
356
|
const panes = listPaneDetails(sessionName);
|
|
326
357
|
|
|
327
358
|
// 1차: title 또는 paneId 직접 매칭
|
|
328
|
-
const direct = panes.find(
|
|
359
|
+
const direct = panes.find(
|
|
360
|
+
(entry) => entry.title === wanted || entry.paneId === wanted,
|
|
361
|
+
);
|
|
329
362
|
if (direct) return direct;
|
|
330
363
|
|
|
331
364
|
// 2차: psmux title 미설정 fallback — "lead"→0, "worker-N"→N 인덱스 매칭
|
|
@@ -340,7 +373,14 @@ function refreshCaptureSnapshot(sessionName, paneNameOrTarget) {
|
|
|
340
373
|
const paneName = pane.title || paneNameOrTarget;
|
|
341
374
|
const logPath = getCaptureLogPath(sessionName, paneName);
|
|
342
375
|
mkdirSync(getCaptureSessionDir(sessionName), { recursive: true });
|
|
343
|
-
const snapshot = psmuxExec([
|
|
376
|
+
const snapshot = psmuxExec([
|
|
377
|
+
"capture-pane",
|
|
378
|
+
"-t",
|
|
379
|
+
pane.paneId,
|
|
380
|
+
"-p",
|
|
381
|
+
"-S",
|
|
382
|
+
"-",
|
|
383
|
+
]);
|
|
344
384
|
writeFileSync(logPath, snapshot, "utf8");
|
|
345
385
|
return { paneId: pane.paneId, paneName, logPath, snapshot };
|
|
346
386
|
}
|
|
@@ -369,7 +409,9 @@ export function sendKeysToPane(paneId, text, submit = true) {
|
|
|
369
409
|
|
|
370
410
|
function toPatternRegExp(pattern) {
|
|
371
411
|
if (pattern instanceof RegExp) {
|
|
372
|
-
const flags = pattern.flags.includes("m")
|
|
412
|
+
const flags = pattern.flags.includes("m")
|
|
413
|
+
? pattern.flags
|
|
414
|
+
: `${pattern.flags}m`;
|
|
373
415
|
return new RegExp(pattern.source, flags);
|
|
374
416
|
}
|
|
375
417
|
return new RegExp(String(pattern), "m");
|
|
@@ -394,13 +436,17 @@ function psmux(args, opts = {}) {
|
|
|
394
436
|
});
|
|
395
437
|
return result != null ? String(result).trim() : "";
|
|
396
438
|
} catch (error) {
|
|
397
|
-
const stderr =
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
439
|
+
const stderr =
|
|
440
|
+
typeof error?.stderr === "string"
|
|
441
|
+
? error.stderr
|
|
442
|
+
: error?.stderr?.toString?.("utf8") || "";
|
|
443
|
+
const stdout =
|
|
444
|
+
typeof error?.stdout === "string"
|
|
445
|
+
? error.stdout
|
|
446
|
+
: error?.stdout?.toString?.("utf8") || "";
|
|
447
|
+
const wrapped = new Error(
|
|
448
|
+
(stderr || stdout || error.message || "psmux command failed").trim(),
|
|
449
|
+
);
|
|
404
450
|
wrapped.status = error.status;
|
|
405
451
|
throw wrapped;
|
|
406
452
|
}
|
|
@@ -439,12 +485,14 @@ export function psmuxExec(args, opts = {}) {
|
|
|
439
485
|
* @returns {{ sessionName: string, panes: string[] }}
|
|
440
486
|
*/
|
|
441
487
|
export function createPsmuxSession(sessionName, opts = {}) {
|
|
442
|
-
const layout =
|
|
488
|
+
const layout =
|
|
489
|
+
opts.layout === "1xN" || opts.layout === "Nx1" ? opts.layout : "2x2";
|
|
443
490
|
const paneCount = Math.max(
|
|
444
491
|
1,
|
|
445
492
|
Number.isFinite(opts.paneCount) ? Math.trunc(opts.paneCount) : 4,
|
|
446
493
|
);
|
|
447
|
-
const limitedPaneCount =
|
|
494
|
+
const limitedPaneCount =
|
|
495
|
+
layout === "2x2" ? Math.min(paneCount, 4) : paneCount;
|
|
448
496
|
const sessionTarget = `${sessionName}:0`;
|
|
449
497
|
|
|
450
498
|
const newSessionArgs = [
|
|
@@ -466,7 +514,17 @@ export function createPsmuxSession(sessionName, opts = {}) {
|
|
|
466
514
|
|
|
467
515
|
// split-window로 생성되는 pane도 동일 셸 사용
|
|
468
516
|
if (PWSH_BIN) {
|
|
469
|
-
try {
|
|
517
|
+
try {
|
|
518
|
+
psmuxExec([
|
|
519
|
+
"set-option",
|
|
520
|
+
"-t",
|
|
521
|
+
sessionName,
|
|
522
|
+
"default-command",
|
|
523
|
+
`${PWSH_BIN} -NoLogo -NoProfile`,
|
|
524
|
+
]);
|
|
525
|
+
} catch {
|
|
526
|
+
/* 미지원 시 무시 */
|
|
527
|
+
}
|
|
470
528
|
}
|
|
471
529
|
|
|
472
530
|
if (layout === "2x2" && limitedPaneCount >= 3) {
|
|
@@ -547,7 +605,11 @@ export function createPsmuxSession(sessionName, opts = {}) {
|
|
|
547
605
|
function collectPanePids(sessionName) {
|
|
548
606
|
try {
|
|
549
607
|
const output = psmuxExec([
|
|
550
|
-
"list-panes",
|
|
608
|
+
"list-panes",
|
|
609
|
+
"-t",
|
|
610
|
+
sessionName,
|
|
611
|
+
"-F",
|
|
612
|
+
"#{pane_pid}",
|
|
551
613
|
]);
|
|
552
614
|
return output
|
|
553
615
|
.split(/\r?\n/)
|
|
@@ -601,9 +663,17 @@ function killOrphanPipeHelpers(sessionName) {
|
|
|
601
663
|
try {
|
|
602
664
|
const output = childProcess.execSync(
|
|
603
665
|
`powershell -NoProfile -WindowStyle Hidden -Command "$ErrorActionPreference='SilentlyContinue'; Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -match 'pipe-pane-capture' -and $_.CommandLine -match 'tfx-headless[/\\\\]${safeSession}' } | Select-Object -ExpandProperty ProcessId"`,
|
|
604
|
-
{
|
|
666
|
+
{
|
|
667
|
+
encoding: "utf8",
|
|
668
|
+
timeout: 8000,
|
|
669
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
670
|
+
windowsHide: true,
|
|
671
|
+
},
|
|
605
672
|
);
|
|
606
|
-
const pids = output
|
|
673
|
+
const pids = output
|
|
674
|
+
.split(/\r?\n/)
|
|
675
|
+
.map((l) => Number.parseInt(l.trim(), 10))
|
|
676
|
+
.filter((p) => Number.isFinite(p) && p > 0);
|
|
607
677
|
for (const pid of pids) {
|
|
608
678
|
killProcessTree(pid);
|
|
609
679
|
}
|
|
@@ -625,7 +695,13 @@ function killOrphanMcpProcesses(sessionName) {
|
|
|
625
695
|
// Hub PID 보호 — Hub 프로세스를 고아로 잘못 식별하지 않도록
|
|
626
696
|
let hubPid = null;
|
|
627
697
|
try {
|
|
628
|
-
const hubPidPath = join(
|
|
698
|
+
const hubPidPath = join(
|
|
699
|
+
homedir(),
|
|
700
|
+
".claude",
|
|
701
|
+
"cache",
|
|
702
|
+
"tfx-hub",
|
|
703
|
+
"hub.pid",
|
|
704
|
+
);
|
|
629
705
|
if (existsSync(hubPidPath)) {
|
|
630
706
|
const hubInfo = JSON.parse(readFileSync(hubPidPath, "utf8"));
|
|
631
707
|
hubPid = Number(hubInfo?.pid);
|
|
@@ -636,9 +712,17 @@ function killOrphanMcpProcesses(sessionName) {
|
|
|
636
712
|
// 세션 결과 디렉토리 패턴으로 MCP 서버 프로세스 식별
|
|
637
713
|
const output = childProcess.execSync(
|
|
638
714
|
`powershell -NoProfile -WindowStyle Hidden -Command "$ErrorActionPreference='SilentlyContinue'; Get-CimInstance Win32_Process | Where-Object { $_.Name -eq 'node.exe' -and $_.CommandLine -match 'tfx-headless[/\\\\]${safeSession}' } | Select-Object -ExpandProperty ProcessId"`,
|
|
639
|
-
{
|
|
715
|
+
{
|
|
716
|
+
encoding: "utf8",
|
|
717
|
+
timeout: 8000,
|
|
718
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
719
|
+
windowsHide: true,
|
|
720
|
+
},
|
|
640
721
|
);
|
|
641
|
-
const pids = output
|
|
722
|
+
const pids = output
|
|
723
|
+
.split(/\r?\n/)
|
|
724
|
+
.map((l) => Number.parseInt(l.trim(), 10))
|
|
725
|
+
.filter((p) => Number.isFinite(p) && p > 0 && p !== hubPid);
|
|
642
726
|
for (const pid of pids) {
|
|
643
727
|
killProcessTree(pid);
|
|
644
728
|
}
|
|
@@ -661,12 +745,15 @@ function detachAttachedClients(sessionName, waitMs = 750) {
|
|
|
661
745
|
|
|
662
746
|
function findFallbackPane(sessionName, excludedPaneId) {
|
|
663
747
|
try {
|
|
664
|
-
const panes = listPaneDetails(sessionName)
|
|
665
|
-
|
|
748
|
+
const panes = listPaneDetails(sessionName).filter(
|
|
749
|
+
(pane) => pane.paneId !== excludedPaneId && !pane.isDead,
|
|
750
|
+
);
|
|
666
751
|
if (panes.length === 0) return null;
|
|
667
|
-
return
|
|
668
|
-
|
|
669
|
-
|
|
752
|
+
return (
|
|
753
|
+
panes.find((pane) => pane.title === "lead") ||
|
|
754
|
+
panes.find((pane) => pane.paneIndex === 0) ||
|
|
755
|
+
panes[0]
|
|
756
|
+
);
|
|
670
757
|
} catch {
|
|
671
758
|
return null;
|
|
672
759
|
}
|
|
@@ -760,11 +847,15 @@ export function capturePsmuxPane(target, lines = 5) {
|
|
|
760
847
|
* @param {string} sessionName
|
|
761
848
|
*/
|
|
762
849
|
export function attachPsmuxSession(sessionName) {
|
|
763
|
-
const result = childProcess.spawnSync(
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
850
|
+
const result = childProcess.spawnSync(
|
|
851
|
+
PSMUX_BIN,
|
|
852
|
+
["attach-session", "-t", sessionName],
|
|
853
|
+
{
|
|
854
|
+
stdio: "inherit",
|
|
855
|
+
timeout: 0,
|
|
856
|
+
windowsHide: false,
|
|
857
|
+
},
|
|
858
|
+
);
|
|
768
859
|
if ((result.status ?? 1) !== 0) {
|
|
769
860
|
throw new Error(`psmux attach 실패 (exit=${result.status})`);
|
|
770
861
|
}
|
|
@@ -777,8 +868,9 @@ export function attachPsmuxSession(sessionName) {
|
|
|
777
868
|
*/
|
|
778
869
|
export function getPsmuxSessionAttachedCount(sessionName) {
|
|
779
870
|
try {
|
|
780
|
-
const session = parseSessionSummaries(psmuxExec(["list-sessions"]))
|
|
781
|
-
|
|
871
|
+
const session = parseSessionSummaries(psmuxExec(["list-sessions"])).find(
|
|
872
|
+
(entry) => entry.sessionName === sessionName,
|
|
873
|
+
);
|
|
782
874
|
return session ? session.attachedCount : null;
|
|
783
875
|
} catch {
|
|
784
876
|
return null;
|
|
@@ -812,12 +904,78 @@ export function configurePsmuxKeybindings(sessionName, opts = {}) {
|
|
|
812
904
|
}
|
|
813
905
|
};
|
|
814
906
|
|
|
815
|
-
bindSafe([
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
907
|
+
bindSafe([
|
|
908
|
+
"bind-key",
|
|
909
|
+
"-T",
|
|
910
|
+
"root",
|
|
911
|
+
"-n",
|
|
912
|
+
"S-Down",
|
|
913
|
+
"if-shell",
|
|
914
|
+
"-F",
|
|
915
|
+
cond,
|
|
916
|
+
bindNext,
|
|
917
|
+
"send-keys S-Down",
|
|
918
|
+
]);
|
|
919
|
+
bindSafe([
|
|
920
|
+
"bind-key",
|
|
921
|
+
"-T",
|
|
922
|
+
"root",
|
|
923
|
+
"-n",
|
|
924
|
+
"S-Up",
|
|
925
|
+
"if-shell",
|
|
926
|
+
"-F",
|
|
927
|
+
cond,
|
|
928
|
+
bindPrev,
|
|
929
|
+
"send-keys S-Up",
|
|
930
|
+
]);
|
|
931
|
+
bindSafe([
|
|
932
|
+
"bind-key",
|
|
933
|
+
"-T",
|
|
934
|
+
"root",
|
|
935
|
+
"-n",
|
|
936
|
+
"S-Right",
|
|
937
|
+
"if-shell",
|
|
938
|
+
"-F",
|
|
939
|
+
cond,
|
|
940
|
+
bindNext,
|
|
941
|
+
"send-keys S-Right",
|
|
942
|
+
]);
|
|
943
|
+
bindSafe([
|
|
944
|
+
"bind-key",
|
|
945
|
+
"-T",
|
|
946
|
+
"root",
|
|
947
|
+
"-n",
|
|
948
|
+
"S-Left",
|
|
949
|
+
"if-shell",
|
|
950
|
+
"-F",
|
|
951
|
+
cond,
|
|
952
|
+
bindPrev,
|
|
953
|
+
"send-keys S-Left",
|
|
954
|
+
]);
|
|
955
|
+
bindSafe([
|
|
956
|
+
"bind-key",
|
|
957
|
+
"-T",
|
|
958
|
+
"root",
|
|
959
|
+
"-n",
|
|
960
|
+
"BTab",
|
|
961
|
+
"if-shell",
|
|
962
|
+
"-F",
|
|
963
|
+
cond,
|
|
964
|
+
bindPrev,
|
|
965
|
+
"send-keys BTab",
|
|
966
|
+
]);
|
|
967
|
+
bindSafe([
|
|
968
|
+
"bind-key",
|
|
969
|
+
"-T",
|
|
970
|
+
"root",
|
|
971
|
+
"-n",
|
|
972
|
+
"Escape",
|
|
973
|
+
"if-shell",
|
|
974
|
+
"-F",
|
|
975
|
+
cond,
|
|
976
|
+
"send-keys C-c",
|
|
977
|
+
"send-keys Escape",
|
|
978
|
+
]);
|
|
821
979
|
|
|
822
980
|
if (taskListCommand) {
|
|
823
981
|
bindSafe([
|
|
@@ -905,7 +1063,10 @@ function resolveCliAbsPath(name) {
|
|
|
905
1063
|
if (_cliPathCache.has(name)) return _cliPathCache.get(name);
|
|
906
1064
|
try {
|
|
907
1065
|
const resolved = childProcess
|
|
908
|
-
.execSync(`${GIT_BASH_BIN_EXEC} -c "which ${name}"`, {
|
|
1066
|
+
.execSync(`${GIT_BASH_BIN_EXEC} -c "which ${name}"`, {
|
|
1067
|
+
encoding: "utf8",
|
|
1068
|
+
timeout: 3000,
|
|
1069
|
+
})
|
|
909
1070
|
.trim();
|
|
910
1071
|
if (resolved) _cliPathCache.set(name, resolved);
|
|
911
1072
|
return resolved || name;
|
|
@@ -922,9 +1083,10 @@ function wrapCliForBash(cmd) {
|
|
|
922
1083
|
if (!cliMatch) return cmd;
|
|
923
1084
|
// Node.js 측에서 절대 경로 resolve → psmux pane의 bash PATH 불일치 문제 해결 (exit 127)
|
|
924
1085
|
const absPath = resolveCliAbsPath(cliMatch[1]);
|
|
925
|
-
const resolved =
|
|
926
|
-
|
|
927
|
-
|
|
1086
|
+
const resolved =
|
|
1087
|
+
absPath !== cliMatch[1]
|
|
1088
|
+
? trimmed.replace(new RegExp(`^${cliMatch[1]}\\b`), absPath)
|
|
1089
|
+
: trimmed;
|
|
928
1090
|
// 단일 따옴표 이스케이프: ' → '\''
|
|
929
1091
|
const escaped = resolved.replace(/'/g, "'\\''");
|
|
930
1092
|
return `${GIT_BASH_BIN_PS} -c '${escaped}'`;
|
|
@@ -962,7 +1124,13 @@ export function dispatchCommand(sessionName, paneNameOrTarget, commandText) {
|
|
|
962
1124
|
* @param {AbortSignal} [opts.signal] — 외부에서 폴링 중단 요청 시 사용
|
|
963
1125
|
* @returns {{ matched: boolean, paneId: string, paneName: string, logPath: string, match: string|null, aborted?: boolean }}
|
|
964
1126
|
*/
|
|
965
|
-
export async function waitForPattern(
|
|
1127
|
+
export async function waitForPattern(
|
|
1128
|
+
sessionName,
|
|
1129
|
+
paneNameOrTarget,
|
|
1130
|
+
pattern,
|
|
1131
|
+
timeoutSec = 300,
|
|
1132
|
+
opts = {},
|
|
1133
|
+
) {
|
|
966
1134
|
ensurePsmuxInstalled();
|
|
967
1135
|
|
|
968
1136
|
// E4 크래시 복구: 초기 resolvePane도 세션 사망을 감지
|
|
@@ -985,11 +1153,14 @@ export async function waitForPattern(sessionName, paneNameOrTarget, pattern, tim
|
|
|
985
1153
|
|
|
986
1154
|
const paneName = pane.title || paneNameOrTarget;
|
|
987
1155
|
// opts.logPath: dispatch 시 확정된 캡처 로그 경로 직접 지정 (타이틀 변경 내성)
|
|
988
|
-
const logPath =
|
|
989
|
-
|
|
990
|
-
|
|
1156
|
+
const logPath =
|
|
1157
|
+
opts.logPath && existsSync(opts.logPath)
|
|
1158
|
+
? opts.logPath
|
|
1159
|
+
: getCaptureLogPath(sessionName, paneName);
|
|
991
1160
|
if (!existsSync(logPath)) {
|
|
992
|
-
throw new Error(
|
|
1161
|
+
throw new Error(
|
|
1162
|
+
`캡처 로그가 없습니다. 먼저 startCapture(${sessionName}, ${paneName})를 호출하세요.`,
|
|
1163
|
+
);
|
|
993
1164
|
}
|
|
994
1165
|
|
|
995
1166
|
const startTime = Date.now();
|
|
@@ -997,18 +1168,39 @@ export async function waitForPattern(sessionName, paneNameOrTarget, pattern, tim
|
|
|
997
1168
|
const regex = toPatternRegExp(pattern);
|
|
998
1169
|
|
|
999
1170
|
if (opts?.signal?.aborted) {
|
|
1000
|
-
return {
|
|
1171
|
+
return {
|
|
1172
|
+
matched: false,
|
|
1173
|
+
paneId: pane.paneId,
|
|
1174
|
+
paneName,
|
|
1175
|
+
logPath,
|
|
1176
|
+
match: null,
|
|
1177
|
+
aborted: true,
|
|
1178
|
+
};
|
|
1001
1179
|
}
|
|
1002
1180
|
|
|
1003
1181
|
while (Date.now() <= deadline) {
|
|
1004
1182
|
if (opts?.signal?.aborted) {
|
|
1005
|
-
return {
|
|
1183
|
+
return {
|
|
1184
|
+
matched: false,
|
|
1185
|
+
paneId: pane.paneId,
|
|
1186
|
+
paneName,
|
|
1187
|
+
logPath,
|
|
1188
|
+
match: null,
|
|
1189
|
+
aborted: true,
|
|
1190
|
+
};
|
|
1006
1191
|
}
|
|
1007
1192
|
// E4 크래시 복구: capture 실패 시 세션 생존 체크
|
|
1008
1193
|
try {
|
|
1009
1194
|
if (opts.logPath) {
|
|
1010
1195
|
// logPath 직접 지정 시 — 셸 타이틀 변경과 무관하게 올바른 파일에 기록
|
|
1011
|
-
const snapshot = psmuxExec([
|
|
1196
|
+
const snapshot = psmuxExec([
|
|
1197
|
+
"capture-pane",
|
|
1198
|
+
"-t",
|
|
1199
|
+
pane.paneId,
|
|
1200
|
+
"-p",
|
|
1201
|
+
"-S",
|
|
1202
|
+
"-",
|
|
1203
|
+
]);
|
|
1012
1204
|
writeFileSync(logPath, snapshot, "utf8");
|
|
1013
1205
|
} else {
|
|
1014
1206
|
refreshCaptureSnapshot(sessionName, pane.paneId);
|
|
@@ -1032,8 +1224,15 @@ export async function waitForPattern(sessionName, paneNameOrTarget, pattern, tim
|
|
|
1032
1224
|
// onPoll 콜백 — 각 폴링 주기마다 중간 상태 전달
|
|
1033
1225
|
if (opts.onPoll) {
|
|
1034
1226
|
try {
|
|
1035
|
-
opts.onPoll({
|
|
1036
|
-
|
|
1227
|
+
opts.onPoll({
|
|
1228
|
+
content,
|
|
1229
|
+
paneId: pane.paneId,
|
|
1230
|
+
paneName,
|
|
1231
|
+
elapsed: Date.now() - startTime,
|
|
1232
|
+
});
|
|
1233
|
+
} catch {
|
|
1234
|
+
/* 콜백 예외는 삼킴 — 폴링 루프 보호 */
|
|
1235
|
+
}
|
|
1037
1236
|
}
|
|
1038
1237
|
|
|
1039
1238
|
const match = regex.exec(content);
|
|
@@ -1052,7 +1251,14 @@ export async function waitForPattern(sessionName, paneNameOrTarget, pattern, tim
|
|
|
1052
1251
|
}
|
|
1053
1252
|
await sleepMsAsync(POLL_INTERVAL_MS);
|
|
1054
1253
|
if (opts?.signal?.aborted) {
|
|
1055
|
-
return {
|
|
1254
|
+
return {
|
|
1255
|
+
matched: false,
|
|
1256
|
+
paneId: pane.paneId,
|
|
1257
|
+
paneName,
|
|
1258
|
+
logPath,
|
|
1259
|
+
match: null,
|
|
1260
|
+
aborted: true,
|
|
1261
|
+
};
|
|
1056
1262
|
}
|
|
1057
1263
|
}
|
|
1058
1264
|
|
|
@@ -1074,19 +1280,38 @@ export async function waitForPattern(sessionName, paneNameOrTarget, pattern, tim
|
|
|
1074
1280
|
* @param {object} [opts] — waitForPattern에 전달할 옵션 (onPoll 등)
|
|
1075
1281
|
* @returns {{ matched: boolean, paneId: string, paneName: string, logPath: string, match: string|null, token: string, exitCode: number|null }}
|
|
1076
1282
|
*/
|
|
1077
|
-
export async function waitForCompletion(
|
|
1283
|
+
export async function waitForCompletion(
|
|
1284
|
+
sessionName,
|
|
1285
|
+
paneNameOrTarget,
|
|
1286
|
+
token,
|
|
1287
|
+
timeoutSec = 300,
|
|
1288
|
+
opts = {},
|
|
1289
|
+
) {
|
|
1078
1290
|
const completionRegex = new RegExp(
|
|
1079
1291
|
`${escapeRegExp(COMPLETION_PREFIX)}${escapeRegExp(token)}:(\\d+)`,
|
|
1080
1292
|
"m",
|
|
1081
1293
|
);
|
|
1082
|
-
const result = await waitForPattern(
|
|
1294
|
+
const result = await waitForPattern(
|
|
1295
|
+
sessionName,
|
|
1296
|
+
paneNameOrTarget,
|
|
1297
|
+
completionRegex,
|
|
1298
|
+
timeoutSec,
|
|
1299
|
+
opts,
|
|
1300
|
+
);
|
|
1083
1301
|
|
|
1084
1302
|
// 타이밍 이슈 대응: matched=false인 경우 500ms 대기 후 최종 1회 캡처 재시도
|
|
1085
1303
|
if (!result.matched && !result.sessionDead && result.logPath) {
|
|
1086
1304
|
await new Promise((r) => setTimeout(r, 500));
|
|
1087
1305
|
try {
|
|
1088
1306
|
const pane = resolvePane(sessionName, paneNameOrTarget);
|
|
1089
|
-
const snapshot = psmuxExec([
|
|
1307
|
+
const snapshot = psmuxExec([
|
|
1308
|
+
"capture-pane",
|
|
1309
|
+
"-t",
|
|
1310
|
+
pane.paneId,
|
|
1311
|
+
"-p",
|
|
1312
|
+
"-S",
|
|
1313
|
+
"-",
|
|
1314
|
+
]);
|
|
1090
1315
|
writeFileSync(result.logPath, snapshot, "utf8");
|
|
1091
1316
|
const content = readCaptureLog(result.logPath);
|
|
1092
1317
|
const retryMatch = completionRegex.exec(content);
|
|
@@ -1125,7 +1350,7 @@ export function spawnWorker(sessionName, workerName, cmd) {
|
|
|
1125
1350
|
if (!hasPsmux()) {
|
|
1126
1351
|
throw new Error(
|
|
1127
1352
|
"psmux가 설치되어 있지 않습니다.\n" +
|
|
1128
|
-
|
|
1353
|
+
`설치 방법:\n${formatPsmuxInstallGuidance(" ")}`,
|
|
1129
1354
|
);
|
|
1130
1355
|
}
|
|
1131
1356
|
|
|
@@ -1155,7 +1380,9 @@ export function spawnWorker(sessionName, workerName, cmd) {
|
|
|
1155
1380
|
psmuxExec(["select-pane", "-t", paneTarget, "-T", workerName]);
|
|
1156
1381
|
return { paneId: paneTarget, workerName };
|
|
1157
1382
|
} catch (err) {
|
|
1158
|
-
throw new Error(
|
|
1383
|
+
throw new Error(
|
|
1384
|
+
`워커 생성 실패 (session=${sessionName}, worker=${workerName}): ${err.message}`,
|
|
1385
|
+
);
|
|
1159
1386
|
}
|
|
1160
1387
|
}
|
|
1161
1388
|
|
|
@@ -1167,7 +1394,9 @@ export function spawnWorker(sessionName, workerName, cmd) {
|
|
|
1167
1394
|
*/
|
|
1168
1395
|
export function getWorkerStatus(sessionName, workerName) {
|
|
1169
1396
|
if (!hasPsmux()) {
|
|
1170
|
-
throw new Error(
|
|
1397
|
+
throw new Error(
|
|
1398
|
+
`psmux 미설치. 설치 방법:\n${formatPsmuxInstallGuidance(" ")}`,
|
|
1399
|
+
);
|
|
1171
1400
|
}
|
|
1172
1401
|
try {
|
|
1173
1402
|
const pane = resolvePane(sessionName, workerName);
|
|
@@ -1180,7 +1409,9 @@ export function getWorkerStatus(sessionName, workerName) {
|
|
|
1180
1409
|
if (err.message.includes("Pane을 찾을 수 없습니다")) {
|
|
1181
1410
|
throw new Error(`워커를 찾을 수 없습니다: ${workerName}`);
|
|
1182
1411
|
}
|
|
1183
|
-
throw new Error(
|
|
1412
|
+
throw new Error(
|
|
1413
|
+
`워커 상태 조회 실패 (session=${sessionName}, worker=${workerName}): ${err.message}`,
|
|
1414
|
+
);
|
|
1184
1415
|
}
|
|
1185
1416
|
}
|
|
1186
1417
|
|
|
@@ -1192,14 +1423,15 @@ export function getWorkerStatus(sessionName, workerName) {
|
|
|
1192
1423
|
*/
|
|
1193
1424
|
export function killWorker(sessionName, workerName) {
|
|
1194
1425
|
if (!hasPsmux()) {
|
|
1195
|
-
throw new Error(
|
|
1426
|
+
throw new Error(
|
|
1427
|
+
`psmux 미설치. 설치 방법:\n${formatPsmuxInstallGuidance(" ")}`,
|
|
1428
|
+
);
|
|
1196
1429
|
}
|
|
1197
1430
|
try {
|
|
1198
1431
|
const { paneId, status } = getWorkerStatus(sessionName, workerName);
|
|
1199
1432
|
const attachedCount = getPsmuxSessionAttachedCount(sessionName);
|
|
1200
|
-
const fallbackPane =
|
|
1201
|
-
? findFallbackPane(sessionName, paneId)
|
|
1202
|
-
: null;
|
|
1433
|
+
const fallbackPane =
|
|
1434
|
+
attachedCount > 0 ? findFallbackPane(sessionName, paneId) : null;
|
|
1203
1435
|
|
|
1204
1436
|
if (fallbackPane?.paneId) {
|
|
1205
1437
|
try {
|
|
@@ -1214,7 +1446,13 @@ export function killWorker(sessionName, workerName) {
|
|
|
1214
1446
|
|
|
1215
1447
|
// pane PID 수집 → 프로세스 트리 정리 (MCP 서버 좀비 방지)
|
|
1216
1448
|
try {
|
|
1217
|
-
const pidOutput = psmuxExec([
|
|
1449
|
+
const pidOutput = psmuxExec([
|
|
1450
|
+
"list-panes",
|
|
1451
|
+
"-t",
|
|
1452
|
+
paneId,
|
|
1453
|
+
"-F",
|
|
1454
|
+
"#{pane_pid}",
|
|
1455
|
+
]);
|
|
1218
1456
|
const pid = Number.parseInt(pidOutput.trim(), 10);
|
|
1219
1457
|
if (Number.isFinite(pid) && pid > 0) killProcessTree(pid);
|
|
1220
1458
|
} catch {
|
|
@@ -1266,7 +1504,9 @@ export function killWorker(sessionName, workerName) {
|
|
|
1266
1504
|
if (err.message.includes("워커를 찾을 수 없습니다")) {
|
|
1267
1505
|
return { killed: true };
|
|
1268
1506
|
}
|
|
1269
|
-
throw new Error(
|
|
1507
|
+
throw new Error(
|
|
1508
|
+
`워커 종료 실패 (session=${sessionName}, worker=${workerName}): ${err.message}`,
|
|
1509
|
+
);
|
|
1270
1510
|
}
|
|
1271
1511
|
}
|
|
1272
1512
|
|
|
@@ -1279,14 +1519,18 @@ export function killWorker(sessionName, workerName) {
|
|
|
1279
1519
|
*/
|
|
1280
1520
|
export function captureWorkerOutput(sessionName, workerName, lines = 50) {
|
|
1281
1521
|
if (!hasPsmux()) {
|
|
1282
|
-
throw new Error(
|
|
1522
|
+
throw new Error(
|
|
1523
|
+
`psmux 미설치. 설치 방법:\n${formatPsmuxInstallGuidance(" ")}`,
|
|
1524
|
+
);
|
|
1283
1525
|
}
|
|
1284
1526
|
try {
|
|
1285
1527
|
const { paneId } = getWorkerStatus(sessionName, workerName);
|
|
1286
1528
|
return psmuxExec(["capture-pane", "-t", paneId, "-p", "-S", `-${lines}`]);
|
|
1287
1529
|
} catch (err) {
|
|
1288
1530
|
if (err.message.includes("워커를 찾을 수 없습니다")) throw err;
|
|
1289
|
-
throw new Error(
|
|
1531
|
+
throw new Error(
|
|
1532
|
+
`출력 캡처 실패 (session=${sessionName}, worker=${workerName}): ${err.message}`,
|
|
1533
|
+
);
|
|
1290
1534
|
}
|
|
1291
1535
|
}
|
|
1292
1536
|
|
|
@@ -1294,123 +1538,169 @@ export function captureWorkerOutput(sessionName, workerName, lines = 50) {
|
|
|
1294
1538
|
|
|
1295
1539
|
if (process.argv[1]?.endsWith("psmux.mjs")) {
|
|
1296
1540
|
(async () => {
|
|
1297
|
-
|
|
1541
|
+
const [, , cmd, ...args] = process.argv;
|
|
1298
1542
|
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1543
|
+
// CLI 인자 파싱 헬퍼
|
|
1544
|
+
function getArg(name) {
|
|
1545
|
+
const idx = args.indexOf(`--${name}`);
|
|
1546
|
+
return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : null;
|
|
1547
|
+
}
|
|
1304
1548
|
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1549
|
+
try {
|
|
1550
|
+
switch (cmd) {
|
|
1551
|
+
case "spawn": {
|
|
1552
|
+
const session = getArg("session");
|
|
1553
|
+
const name = getArg("name");
|
|
1554
|
+
const workerCmd = getArg("cmd");
|
|
1555
|
+
if (!session || !name || !workerCmd) {
|
|
1556
|
+
console.error(
|
|
1557
|
+
"사용법: node psmux.mjs spawn --session <세션> --name <워커명> --cmd <커맨드>",
|
|
1558
|
+
);
|
|
1559
|
+
process.exit(1);
|
|
1560
|
+
}
|
|
1561
|
+
console.log(
|
|
1562
|
+
JSON.stringify(spawnWorker(session, name, workerCmd), null, 2),
|
|
1563
|
+
);
|
|
1564
|
+
break;
|
|
1314
1565
|
}
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1566
|
+
case "status": {
|
|
1567
|
+
const session = getArg("session");
|
|
1568
|
+
const name = getArg("name");
|
|
1569
|
+
if (!session || !name) {
|
|
1570
|
+
console.error(
|
|
1571
|
+
"사용법: node psmux.mjs status --session <세션> --name <워커명>",
|
|
1572
|
+
);
|
|
1573
|
+
process.exit(1);
|
|
1574
|
+
}
|
|
1575
|
+
console.log(JSON.stringify(getWorkerStatus(session, name), null, 2));
|
|
1576
|
+
break;
|
|
1324
1577
|
}
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1578
|
+
case "kill": {
|
|
1579
|
+
const session = getArg("session");
|
|
1580
|
+
const name = getArg("name");
|
|
1581
|
+
if (!session || !name) {
|
|
1582
|
+
console.error(
|
|
1583
|
+
"사용법: node psmux.mjs kill --session <세션> --name <워커명>",
|
|
1584
|
+
);
|
|
1585
|
+
process.exit(1);
|
|
1586
|
+
}
|
|
1587
|
+
console.log(JSON.stringify(killWorker(session, name), null, 2));
|
|
1588
|
+
break;
|
|
1334
1589
|
}
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1590
|
+
case "output": {
|
|
1591
|
+
const session = getArg("session");
|
|
1592
|
+
const name = getArg("name");
|
|
1593
|
+
const lines = parseInt(getArg("lines") || "50", 10);
|
|
1594
|
+
if (!session || !name) {
|
|
1595
|
+
console.error(
|
|
1596
|
+
"사용법: node psmux.mjs output --session <세션> --name <워커명> [--lines <줄수>]",
|
|
1597
|
+
);
|
|
1598
|
+
process.exit(1);
|
|
1599
|
+
}
|
|
1600
|
+
console.log(captureWorkerOutput(session, name, lines));
|
|
1601
|
+
break;
|
|
1345
1602
|
}
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1603
|
+
case "capture-start": {
|
|
1604
|
+
const session = getArg("session");
|
|
1605
|
+
const name = getArg("name");
|
|
1606
|
+
if (!session || !name) {
|
|
1607
|
+
console.error(
|
|
1608
|
+
"사용법: node psmux.mjs capture-start --session <세션> --name <pane>",
|
|
1609
|
+
);
|
|
1610
|
+
process.exit(1);
|
|
1611
|
+
}
|
|
1612
|
+
console.log(JSON.stringify(startCapture(session, name), null, 2));
|
|
1613
|
+
break;
|
|
1355
1614
|
}
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1615
|
+
case "dispatch": {
|
|
1616
|
+
const session = getArg("session");
|
|
1617
|
+
const name = getArg("name");
|
|
1618
|
+
const commandText = getArg("command");
|
|
1619
|
+
if (!session || !name || !commandText) {
|
|
1620
|
+
console.error(
|
|
1621
|
+
"사용법: node psmux.mjs dispatch --session <세션> --name <pane> --command <PowerShell 명령>",
|
|
1622
|
+
);
|
|
1623
|
+
process.exit(1);
|
|
1624
|
+
}
|
|
1625
|
+
console.log(
|
|
1626
|
+
JSON.stringify(
|
|
1627
|
+
dispatchCommand(session, name, commandText),
|
|
1628
|
+
null,
|
|
1629
|
+
2,
|
|
1630
|
+
),
|
|
1631
|
+
);
|
|
1632
|
+
break;
|
|
1366
1633
|
}
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1634
|
+
case "wait-pattern": {
|
|
1635
|
+
const session = getArg("session");
|
|
1636
|
+
const name = getArg("name");
|
|
1637
|
+
const pattern = getArg("pattern");
|
|
1638
|
+
const timeoutSec = parseInt(getArg("timeout") || "300", 10);
|
|
1639
|
+
if (!session || !name || !pattern) {
|
|
1640
|
+
console.error(
|
|
1641
|
+
"사용법: node psmux.mjs wait-pattern --session <세션> --name <pane> --pattern <정규식> [--timeout <초>]",
|
|
1642
|
+
);
|
|
1643
|
+
process.exit(1);
|
|
1644
|
+
}
|
|
1645
|
+
const result = await waitForPattern(
|
|
1646
|
+
session,
|
|
1647
|
+
name,
|
|
1648
|
+
pattern,
|
|
1649
|
+
timeoutSec,
|
|
1650
|
+
);
|
|
1651
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1652
|
+
if (!result.matched) process.exit(2);
|
|
1653
|
+
break;
|
|
1378
1654
|
}
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1655
|
+
case "wait-completion": {
|
|
1656
|
+
const session = getArg("session");
|
|
1657
|
+
const name = getArg("name");
|
|
1658
|
+
const token = getArg("token");
|
|
1659
|
+
const timeoutSec = parseInt(getArg("timeout") || "300", 10);
|
|
1660
|
+
if (!session || !name || !token) {
|
|
1661
|
+
console.error(
|
|
1662
|
+
"사용법: node psmux.mjs wait-completion --session <세션> --name <pane> --token <토큰> [--timeout <초>]",
|
|
1663
|
+
);
|
|
1664
|
+
process.exit(1);
|
|
1665
|
+
}
|
|
1666
|
+
const result = await waitForCompletion(
|
|
1667
|
+
session,
|
|
1668
|
+
name,
|
|
1669
|
+
token,
|
|
1670
|
+
timeoutSec,
|
|
1671
|
+
);
|
|
1672
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1673
|
+
if (!result.matched) process.exit(2);
|
|
1674
|
+
break;
|
|
1392
1675
|
}
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1676
|
+
default:
|
|
1677
|
+
console.error(
|
|
1678
|
+
"사용법: node psmux.mjs spawn|status|kill|output|capture-start|dispatch|wait-pattern|wait-completion [args]",
|
|
1679
|
+
);
|
|
1680
|
+
console.error("");
|
|
1681
|
+
console.error(
|
|
1682
|
+
" spawn --session <세션> --name <워커명> --cmd <커맨드>",
|
|
1683
|
+
);
|
|
1684
|
+
console.error(" status --session <세션> --name <워커명>");
|
|
1685
|
+
console.error(" kill --session <세션> --name <워커명>");
|
|
1686
|
+
console.error(
|
|
1687
|
+
" output --session <세션> --name <워커명> [--lines <줄수>]",
|
|
1688
|
+
);
|
|
1689
|
+
console.error(" capture-start --session <세션> --name <pane>");
|
|
1690
|
+
console.error(
|
|
1691
|
+
" dispatch --session <세션> --name <pane> --command <PowerShell 명령>",
|
|
1692
|
+
);
|
|
1693
|
+
console.error(
|
|
1694
|
+
" wait-pattern --session <세션> --name <pane> --pattern <정규식> [--timeout <초>]",
|
|
1695
|
+
);
|
|
1696
|
+
console.error(
|
|
1697
|
+
" wait-completion --session <세션> --name <pane> --token <토큰> [--timeout <초>]",
|
|
1698
|
+
);
|
|
1699
|
+
process.exit(1);
|
|
1397
1700
|
}
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
console.error(" spawn --session <세션> --name <워커명> --cmd <커맨드>");
|
|
1402
|
-
console.error(" status --session <세션> --name <워커명>");
|
|
1403
|
-
console.error(" kill --session <세션> --name <워커명>");
|
|
1404
|
-
console.error(" output --session <세션> --name <워커명> [--lines <줄수>]");
|
|
1405
|
-
console.error(" capture-start --session <세션> --name <pane>");
|
|
1406
|
-
console.error(" dispatch --session <세션> --name <pane> --command <PowerShell 명령>");
|
|
1407
|
-
console.error(" wait-pattern --session <세션> --name <pane> --pattern <정규식> [--timeout <초>]");
|
|
1408
|
-
console.error(" wait-completion --session <세션> --name <pane> --token <토큰> [--timeout <초>]");
|
|
1409
|
-
process.exit(1);
|
|
1701
|
+
} catch (err) {
|
|
1702
|
+
console.error(`오류: ${err.message}`);
|
|
1703
|
+
process.exit(1);
|
|
1410
1704
|
}
|
|
1411
|
-
} catch (err) {
|
|
1412
|
-
console.error(`오류: ${err.message}`);
|
|
1413
|
-
process.exit(1);
|
|
1414
|
-
}
|
|
1415
1705
|
})();
|
|
1416
1706
|
}
|