triflux 6.0.0 → 6.0.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.
- package/hub/team/headless.mjs +14 -9
- package/package.json +1 -1
- package/scripts/preinstall.mjs +80 -43
package/hub/team/headless.mjs
CHANGED
|
@@ -81,13 +81,18 @@ export async function runHeadless(sessionName, assignments, opts = {}) {
|
|
|
81
81
|
|
|
82
82
|
mkdirSync(RESULT_DIR, { recursive: true });
|
|
83
83
|
|
|
84
|
+
// onProgress 예외를 삼켜 실행 흐름 보호 (onPoll과 동일 패턴)
|
|
85
|
+
const safeProgress = onProgress
|
|
86
|
+
? (event) => { try { onProgress(event); } catch { /* 콜백 예외 삼킴 */ } }
|
|
87
|
+
: null;
|
|
88
|
+
|
|
84
89
|
let dispatches;
|
|
85
90
|
|
|
86
91
|
if (progressive) {
|
|
87
92
|
// ─── 실시간 스플릿 모드: lead pane만 생성 후, 워커를 하나씩 추가 ───
|
|
88
93
|
const session = createPsmuxSession(sessionName, { layout, paneCount: 1 });
|
|
89
94
|
applyTrifluxTheme(sessionName);
|
|
90
|
-
if (
|
|
95
|
+
if (safeProgress) safeProgress({ type: "session_created", sessionName, panes: session.panes });
|
|
91
96
|
|
|
92
97
|
dispatches = assignments.map((assignment, i) => {
|
|
93
98
|
const paneName = `worker-${i + 1}`;
|
|
@@ -104,7 +109,7 @@ export async function runHeadless(sessionName, assignments, opts = {}) {
|
|
|
104
109
|
// 타이틀 설정
|
|
105
110
|
try { psmuxExec(["select-pane", "-t", newPaneId, "-T", paneTitle]); } catch { /* 무시 */ }
|
|
106
111
|
|
|
107
|
-
if (
|
|
112
|
+
if (safeProgress) safeProgress({ type: "worker_added", paneName, cli: assignment.cli, paneTitle });
|
|
108
113
|
|
|
109
114
|
// 캡처 시작 + 명령 dispatch (paneId 직접 사용 — resolvePane race 회피)
|
|
110
115
|
const resultFile = join(RESULT_DIR, `${sessionName}-${paneName}.txt`).replace(/\\/g, "/");
|
|
@@ -112,7 +117,7 @@ export async function runHeadless(sessionName, assignments, opts = {}) {
|
|
|
112
117
|
startCapture(sessionName, newPaneId);
|
|
113
118
|
const dispatch = dispatchCommand(sessionName, newPaneId, cmd);
|
|
114
119
|
|
|
115
|
-
if (
|
|
120
|
+
if (safeProgress) safeProgress({ type: "dispatched", paneName, cli: assignment.cli });
|
|
116
121
|
|
|
117
122
|
return { ...dispatch, paneId: newPaneId, paneName, resultFile, cli: assignment.cli, role: assignment.role };
|
|
118
123
|
});
|
|
@@ -125,7 +130,7 @@ export async function runHeadless(sessionName, assignments, opts = {}) {
|
|
|
125
130
|
const paneCount = assignments.length + 1;
|
|
126
131
|
const session = createPsmuxSession(sessionName, { layout, paneCount });
|
|
127
132
|
applyTrifluxTheme(sessionName);
|
|
128
|
-
if (
|
|
133
|
+
if (safeProgress) safeProgress({ type: "session_created", sessionName, panes: session.panes });
|
|
129
134
|
|
|
130
135
|
dispatches = assignments.map((assignment, i) => {
|
|
131
136
|
const paneName = `worker-${i + 1}`;
|
|
@@ -137,7 +142,7 @@ export async function runHeadless(sessionName, assignments, opts = {}) {
|
|
|
137
142
|
// 리네임하면 waitForCompletion이 "codex (role).log"를 찾지만 실제는 "worker-N.log"로 불일치
|
|
138
143
|
// progressive 모드에서는 split-window 시 새 pane에 바로 타이틀이 설정되므로 문제없음
|
|
139
144
|
|
|
140
|
-
if (
|
|
145
|
+
if (safeProgress) safeProgress({ type: "dispatched", paneName, cli: assignment.cli });
|
|
141
146
|
|
|
142
147
|
return { ...dispatch, paneName, resultFile, cli: assignment.cli, role: assignment.role };
|
|
143
148
|
});
|
|
@@ -147,14 +152,14 @@ export async function runHeadless(sessionName, assignments, opts = {}) {
|
|
|
147
152
|
const results = await Promise.all(dispatches.map(async (d) => {
|
|
148
153
|
// onPoll → onProgress 변환 (throttle by progressIntervalSec)
|
|
149
154
|
const pollOpts = {};
|
|
150
|
-
if (
|
|
155
|
+
if (safeProgress && progressIntervalSec > 0) {
|
|
151
156
|
let lastProgressAt = 0;
|
|
152
157
|
const intervalMs = progressIntervalSec * 1000;
|
|
153
158
|
pollOpts.onPoll = ({ content }) => {
|
|
154
159
|
const now = Date.now();
|
|
155
160
|
if (now - lastProgressAt >= intervalMs) {
|
|
156
161
|
lastProgressAt = now;
|
|
157
|
-
|
|
162
|
+
safeProgress({
|
|
158
163
|
type: "progress",
|
|
159
164
|
paneName: d.paneName,
|
|
160
165
|
cli: d.cli,
|
|
@@ -170,8 +175,8 @@ export async function runHeadless(sessionName, assignments, opts = {}) {
|
|
|
170
175
|
? readResult(d.resultFile, d.paneId)
|
|
171
176
|
: "";
|
|
172
177
|
|
|
173
|
-
if (
|
|
174
|
-
|
|
178
|
+
if (safeProgress) {
|
|
179
|
+
safeProgress({
|
|
175
180
|
type: "completed",
|
|
176
181
|
paneName: d.paneName,
|
|
177
182
|
cli: d.cli,
|
package/package.json
CHANGED
package/scripts/preinstall.mjs
CHANGED
|
@@ -1,43 +1,80 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// npm install 전 Hub를 안전하게 중지하여 EBUSY 방지
|
|
3
|
-
// better-sqlite3.node 파일이 Hub 프로세스에 의해 잠기면 npm이 덮어쓸 수 없음
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
// PID 파일 정리
|
|
33
|
-
try { unlinkSync(HUB_PID_FILE); } catch {}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// npm install 전 Hub를 안전하게 중지하여 EBUSY 방지
|
|
3
|
+
// better-sqlite3.node 파일이 Hub 프로세스에 의해 잠기면 npm이 덮어쓸 수 없음
|
|
4
|
+
//
|
|
5
|
+
// v6.0.0: taskkill /T /F + Atomics.wait sleep + 파일 잠금 확인
|
|
6
|
+
// (bin/triflux.mjs stopHubForUpdate 패턴과 동일)
|
|
7
|
+
|
|
8
|
+
import { existsSync, readFileSync, unlinkSync, openSync, closeSync } from "fs";
|
|
9
|
+
import { join, dirname } from "path";
|
|
10
|
+
import { homedir } from "os";
|
|
11
|
+
import { execFileSync } from "child_process";
|
|
12
|
+
import { fileURLToPath } from "url";
|
|
13
|
+
|
|
14
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const PKG_ROOT = join(__dirname, "..");
|
|
16
|
+
const HUB_PID_FILE = join(homedir(), ".claude", "cache", "tfx-hub", "hub.pid");
|
|
17
|
+
|
|
18
|
+
function sleepMs(ms) {
|
|
19
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function stopHub() {
|
|
23
|
+
if (!existsSync(HUB_PID_FILE)) return;
|
|
24
|
+
|
|
25
|
+
let info;
|
|
26
|
+
try {
|
|
27
|
+
info = JSON.parse(readFileSync(HUB_PID_FILE, "utf8"));
|
|
28
|
+
const pid = Number(info?.pid);
|
|
29
|
+
if (!Number.isFinite(pid) || pid <= 0) return;
|
|
30
|
+
process.kill(pid, 0); // 프로세스 존재 확인
|
|
31
|
+
} catch {
|
|
32
|
+
// 프로세스 없음 또는 PID 파일 손상 — PID 파일만 정리
|
|
33
|
+
try { unlinkSync(HUB_PID_FILE); } catch {}
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const pid = Number(info.pid);
|
|
38
|
+
|
|
39
|
+
// 1단계: 프로세스 종료 — Windows는 taskkill, Unix는 SIGTERM
|
|
40
|
+
try {
|
|
41
|
+
if (process.platform === "win32") {
|
|
42
|
+
execFileSync("taskkill", ["/PID", String(pid), "/T", "/F"], {
|
|
43
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
44
|
+
timeout: 10000,
|
|
45
|
+
windowsHide: true,
|
|
46
|
+
});
|
|
47
|
+
} else {
|
|
48
|
+
process.kill(pid, "SIGTERM");
|
|
49
|
+
}
|
|
50
|
+
} catch {
|
|
51
|
+
// taskkill 실패 시 SIGKILL fallback
|
|
52
|
+
try { process.kill(pid, "SIGKILL"); } catch {}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 2단계: 프로세스 종료 대기 (최대 5초, 500ms 간격)
|
|
56
|
+
for (let i = 0; i < 10; i++) {
|
|
57
|
+
sleepMs(500);
|
|
58
|
+
try { process.kill(pid, 0); } catch { break; }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 3단계: better-sqlite3.node 파일 잠금 해제 확인 (최대 3초)
|
|
62
|
+
const sqliteNode = join(PKG_ROOT, "node_modules", "better-sqlite3", "build", "Release", "better_sqlite3.node");
|
|
63
|
+
if (existsSync(sqliteNode)) {
|
|
64
|
+
for (let i = 0; i < 6; i++) {
|
|
65
|
+
try {
|
|
66
|
+
const fd = openSync(sqliteNode, "r");
|
|
67
|
+
closeSync(fd);
|
|
68
|
+
break; // 열림 = 잠금 해제됨
|
|
69
|
+
} catch {
|
|
70
|
+
sleepMs(500);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 4단계: PID 파일 정리 (종료 확인 후)
|
|
76
|
+
try { unlinkSync(HUB_PID_FILE); } catch {}
|
|
77
|
+
console.log(`[triflux preinstall] Hub 중지 완료 (PID ${pid})`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
stopHub();
|