@triflux/remote 10.0.0 → 10.1.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/hub/index.mjs +5 -5
- package/hub/public/dashboard.html +7 -7
- package/hub/server.mjs +94 -52
- package/hub/store-adapter.mjs +51 -20
- package/hub/store.mjs +8 -8
- package/hub/team/cli/commands/start/start-headless.mjs +5 -3
- package/hub/team/cli/services/hub-client.mjs +1 -1
- package/hub/team/conductor-mesh-bridge.mjs +121 -0
- package/hub/team/conductor.mjs +671 -671
- package/hub/team/dashboard-open.mjs +1 -1
- package/hub/team/headless.mjs +1156 -1151
- package/hub/team/notify.mjs +1 -1
- package/hub/team/orchestrator.mjs +2 -2
- package/hub/team/psmux.mjs +67 -5
- package/hub/team/remote-probe.mjs +1 -1
- package/hub/team/remote-session.mjs +5 -2
- package/hub/team/remote-watcher.mjs +1 -1
- package/hub/team/session.mjs +1 -1
- package/hub/team/swarm-hypervisor.mjs +612 -0
- package/hub/team/swarm-locks.mjs +204 -0
- package/hub/team/swarm-planner.mjs +260 -0
- package/hub/team/swarm-reconciler.mjs +137 -0
- package/hub/team/tui-remote-adapter.mjs +3 -3
- package/hub/team/tui-viewer.mjs +0 -1
- package/hub/team/tui.mjs +12 -15
- package/hub/team/worktree-lifecycle.mjs +229 -0
- package/hub/team/wt-manager.mjs +407 -0
- package/hub/team/wt-templates.json +43 -0
- package/hub/workers/codex-mcp.mjs +507 -510
- package/hub/workers/delegator-mcp.mjs +1 -2
- package/package.json +11 -3
package/hub/team/notify.mjs
CHANGED
|
@@ -50,7 +50,7 @@ export function decomposeTask(taskDescription, agentCount) {
|
|
|
50
50
|
* @returns {string}
|
|
51
51
|
*/
|
|
52
52
|
export function buildLeadPrompt(taskDescription, config) {
|
|
53
|
-
const { agentId,
|
|
53
|
+
const { agentId, teammateMode = "tmux", workers = [] } = config;
|
|
54
54
|
|
|
55
55
|
const roster = workers
|
|
56
56
|
.map((w, i) => `${i + 1}. ${w.agentId} (${w.cli}) — ${w.subtask}`)
|
|
@@ -92,7 +92,7 @@ ${roster}
|
|
|
92
92
|
export function buildPrompt(subtask, config) {
|
|
93
93
|
const { cli, agentId, hubUrl } = config;
|
|
94
94
|
|
|
95
|
-
const
|
|
95
|
+
const _hubBase = hubUrl.replace("/mcp", "");
|
|
96
96
|
|
|
97
97
|
const bridgePath = "node hub/bridge.mjs";
|
|
98
98
|
|
package/hub/team/psmux.mjs
CHANGED
|
@@ -196,7 +196,12 @@ function ensureCaptureHelper() {
|
|
|
196
196
|
"}",
|
|
197
197
|
"",
|
|
198
198
|
"# Force UTF-8 encoding — CP949 등 non-UTF-8 codepage에서 Gemini stdout 캡처 실패 방지",
|
|
199
|
+
"# InputEncoding: pipe-pane에서 읽어들이는 바이트 해석",
|
|
200
|
+
"# OutputEncoding: 네이티브 명령(gemini/codex CLI)의 stdout 디코딩",
|
|
201
|
+
"# $OutputEncoding: PowerShell 파이프라인 기본 인코딩 (BOM 없는 UTF-8)",
|
|
199
202
|
"[Console]::InputEncoding = [System.Text.Encoding]::UTF8",
|
|
203
|
+
"[Console]::OutputEncoding = [System.Text.Encoding]::UTF8",
|
|
204
|
+
"$OutputEncoding = [System.Text.UTF8Encoding]::new($false)",
|
|
200
205
|
"",
|
|
201
206
|
"$reader = [Console]::In",
|
|
202
207
|
"while (($line = $reader.ReadLine()) -ne $null) {",
|
|
@@ -514,6 +519,23 @@ export function createPsmuxSession(sessionName, opts = {}) {
|
|
|
514
519
|
psmuxExec(["select-pane", "-t", pane, "-T", toPaneTitle(index)]);
|
|
515
520
|
});
|
|
516
521
|
|
|
522
|
+
// CP949 등 non-UTF-8 codepage 환경에서 CLI stdout 깨짐 방지:
|
|
523
|
+
// pane 생성 직후 UTF-8 인코딩을 강제 설정한다.
|
|
524
|
+
// chcp 65001: 프로세스 코드 페이지를 UTF-8로 전환
|
|
525
|
+
// OutputEncoding: 네이티브 명령(gemini/codex) stdout 디코딩
|
|
526
|
+
// $OutputEncoding: PowerShell 파이프라인 기본 인코딩
|
|
527
|
+
if (IS_WINDOWS) {
|
|
528
|
+
const encodingInit = [
|
|
529
|
+
"chcp 65001 > $null",
|
|
530
|
+
"[Console]::InputEncoding = [System.Text.Encoding]::UTF8",
|
|
531
|
+
"[Console]::OutputEncoding = [System.Text.Encoding]::UTF8",
|
|
532
|
+
"$OutputEncoding = [System.Text.UTF8Encoding]::new($false)",
|
|
533
|
+
].join("; ");
|
|
534
|
+
panes.forEach((paneId) => {
|
|
535
|
+
sendLiteralToPane(paneId, encodingInit, true);
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
|
|
517
539
|
return { sessionName, panes };
|
|
518
540
|
}
|
|
519
541
|
|
|
@@ -857,15 +879,55 @@ export function startCapture(sessionName, paneNameOrTarget) {
|
|
|
857
879
|
* @param {string} cmd
|
|
858
880
|
* @returns {string}
|
|
859
881
|
*/
|
|
882
|
+
// Windows: PowerShell의 bash가 WSL을 가리킬 수 있음. Git Bash를 명시적으로 사용.
|
|
883
|
+
// WSL bash에서는 /c/Users/... 경로가 유효하지 않아 exit 127 발생.
|
|
884
|
+
// psmux pane 용 (PowerShell → bash 실행): & "path" 형식
|
|
885
|
+
// Node.js execSync 용 (직접 실행): "path" 형식
|
|
886
|
+
const _gitBashPath = (() => {
|
|
887
|
+
if (!IS_WINDOWS) return null;
|
|
888
|
+
const candidates = [
|
|
889
|
+
"C:\\Program Files\\Git\\usr\\bin\\bash.exe",
|
|
890
|
+
"C:\\Program Files (x86)\\Git\\usr\\bin\\bash.exe",
|
|
891
|
+
];
|
|
892
|
+
for (const p of candidates) {
|
|
893
|
+
if (existsSync(p)) return p;
|
|
894
|
+
}
|
|
895
|
+
return null;
|
|
896
|
+
})();
|
|
897
|
+
// PowerShell call operator(&)가 필요: & "C:\...\bash.exe" -c '...'
|
|
898
|
+
const GIT_BASH_BIN_PS = _gitBashPath ? `& "${_gitBashPath}"` : "bash";
|
|
899
|
+
// Node.js execSync에서 직접 실행할 때는 따옴표만
|
|
900
|
+
const GIT_BASH_BIN_EXEC = _gitBashPath ? `"${_gitBashPath}"` : "bash";
|
|
901
|
+
|
|
902
|
+
// CLI 바이너리 절대 경로 캐시 (세션 중 1회만 resolve)
|
|
903
|
+
const _cliPathCache = new Map();
|
|
904
|
+
function resolveCliAbsPath(name) {
|
|
905
|
+
if (_cliPathCache.has(name)) return _cliPathCache.get(name);
|
|
906
|
+
try {
|
|
907
|
+
const resolved = childProcess
|
|
908
|
+
.execSync(`${GIT_BASH_BIN_EXEC} -c "which ${name}"`, { encoding: "utf8", timeout: 3000 })
|
|
909
|
+
.trim();
|
|
910
|
+
if (resolved) _cliPathCache.set(name, resolved);
|
|
911
|
+
return resolved || name;
|
|
912
|
+
} catch {
|
|
913
|
+
return name;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
|
|
860
917
|
function wrapCliForBash(cmd) {
|
|
861
918
|
const trimmed = cmd.trimStart();
|
|
862
919
|
// PowerShell 구문(Clear-Host, Get-Content 등) 또는 completion token이 포함되면 PowerShell 직통
|
|
863
920
|
if (/Clear-Host|Get-Content|__TRIFLUX_DONE__/i.test(trimmed)) return cmd;
|
|
864
|
-
const
|
|
865
|
-
if (!
|
|
921
|
+
const cliMatch = trimmed.match(/^(codex|gemini)\b/u);
|
|
922
|
+
if (!cliMatch) return cmd;
|
|
923
|
+
// Node.js 측에서 절대 경로 resolve → psmux pane의 bash PATH 불일치 문제 해결 (exit 127)
|
|
924
|
+
const absPath = resolveCliAbsPath(cliMatch[1]);
|
|
925
|
+
const resolved = absPath !== cliMatch[1]
|
|
926
|
+
? trimmed.replace(new RegExp(`^${cliMatch[1]}\\b`), absPath)
|
|
927
|
+
: trimmed;
|
|
866
928
|
// 단일 따옴표 이스케이프: ' → '\''
|
|
867
|
-
const escaped =
|
|
868
|
-
return
|
|
929
|
+
const escaped = resolved.replace(/'/g, "'\\''");
|
|
930
|
+
return `${GIT_BASH_BIN_PS} -c '${escaped}'`;
|
|
869
931
|
}
|
|
870
932
|
|
|
871
933
|
export function dispatchCommand(sessionName, paneNameOrTarget, commandText) {
|
|
@@ -1230,7 +1292,7 @@ export function captureWorkerOutput(sessionName, workerName, lines = 50) {
|
|
|
1230
1292
|
|
|
1231
1293
|
// ─── CLI 진입점 ───
|
|
1232
1294
|
|
|
1233
|
-
if (process.argv[1]
|
|
1295
|
+
if (process.argv[1]?.endsWith("psmux.mjs")) {
|
|
1234
1296
|
(async () => {
|
|
1235
1297
|
const [, , cmd, ...args] = process.argv;
|
|
1236
1298
|
|
|
@@ -14,7 +14,7 @@ import { detectInputWait, PROBE_DEFAULTS } from './health-probe.mjs';
|
|
|
14
14
|
const COMPLETION_TOKEN_RE = /__TRIFLUX_DONE__/;
|
|
15
15
|
|
|
16
16
|
/** 프롬프트 idle 패턴 (Claude Code 프롬프트 복귀) */
|
|
17
|
-
const
|
|
17
|
+
const _PROMPT_IDLE_RE = /(\u276f|\u2795|>\s*$)/;
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* SSH 경유로 원격 psmux capture-pane 실행.
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { execFileSync } from 'node:child_process';
|
|
6
6
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
7
7
|
import { basename, join, posix as posixPath, win32 as win32Path } from 'node:path';
|
|
8
|
+
import { execSshWithRetry } from '@triflux/core/hub/lib/ssh-retry.mjs';
|
|
8
9
|
|
|
9
10
|
const REMOTE_ENV_TTL_MS = 86_400_000; // 24h
|
|
10
11
|
const REMOTE_STAGE_ROOT = 'tfx-remote';
|
|
@@ -84,8 +85,9 @@ function probeRemoteEnvViaPwsh(host) {
|
|
|
84
85
|
].join('; ');
|
|
85
86
|
|
|
86
87
|
try {
|
|
87
|
-
const output =
|
|
88
|
+
const output = execSshWithRetry([host, 'pwsh', '-NoProfile', '-Command', command], {
|
|
88
89
|
encoding: 'utf8', timeout: 15000, stdio: ['pipe', 'pipe', 'pipe'],
|
|
90
|
+
maxRetries: 2, baseDelayMs: 1000,
|
|
89
91
|
});
|
|
90
92
|
return normalizePwshProbeEnv(parseProbeLines(output));
|
|
91
93
|
} catch {
|
|
@@ -102,8 +104,9 @@ function probeRemoteEnvViaPosix(host) {
|
|
|
102
104
|
].join('\n');
|
|
103
105
|
|
|
104
106
|
try {
|
|
105
|
-
const output =
|
|
107
|
+
const output = execSshWithRetry([host, 'sh'], {
|
|
106
108
|
encoding: 'utf8', timeout: 15000, input: script,
|
|
109
|
+
maxRetries: 2, baseDelayMs: 1000,
|
|
107
110
|
});
|
|
108
111
|
return normalizePosixProbeEnv(parseProbeLines(output));
|
|
109
112
|
} catch {
|
|
@@ -40,7 +40,7 @@ const PROMPT_IDLE_PATTERNS = Object.freeze([
|
|
|
40
40
|
]);
|
|
41
41
|
|
|
42
42
|
/** @deprecated shellQuoteForHost(value, os) 사용 권장 — OS-aware 쿼팅 */
|
|
43
|
-
function
|
|
43
|
+
function _shellQuote(value, os) {
|
|
44
44
|
if (os) return shellQuoteForHost(value, os);
|
|
45
45
|
return `'${String(value).replace(/'/g, "'\\''")}'`;
|
|
46
46
|
}
|
package/hub/team/session.mjs
CHANGED
|
@@ -481,7 +481,7 @@ export function configureTeammateKeybindings(sessionName, opts = {}) {
|
|
|
481
481
|
if (taskListCommand) {
|
|
482
482
|
const escaped = taskListCommand.replace(/'/g, "'\\''");
|
|
483
483
|
try {
|
|
484
|
-
tmux(`bind-key -T root -n C-t if-shell -F '${cond}'
|
|
484
|
+
tmux(`bind-key -T root -n C-t if-shell -F '${cond}' "display-popup -E '${escaped}'" "send-keys C-t"`);
|
|
485
485
|
} catch {
|
|
486
486
|
tmux(`bind-key -T root -n C-t if-shell -F '${cond}' 'display-message "tfx multi tasks 명령으로 태스크 확인"' 'send-keys C-t'`);
|
|
487
487
|
}
|