coxpit 3.4.0 → 3.4.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.
- package/package.json +1 -1
- package/src/board.ts +14 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "coxpit",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.1",
|
|
4
4
|
"description": "Self-hosted cockpit for running a fleet of AI coding agents across your own machines — parallel worktree runs, live board, compare & merge, web terminal, design capture.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/board.ts
CHANGED
|
@@ -1158,14 +1158,20 @@ let termRunId = null, termClosing = false, termRetry = 0, termRetryTimer = null;
|
|
|
1158
1158
|
function termConnect(){
|
|
1159
1159
|
if (termRunId==null || termClosing || !termObj) return;
|
|
1160
1160
|
const proto = location.protocol==='https:'?'wss':'ws';
|
|
1161
|
-
|
|
1161
|
+
// 소켓 정체성 가드 — close 이벤트는 비동기라 termClosing 토글만으론 못 막는다.
|
|
1162
|
+
// 대체된(sock!==termWS) 소켓은 출력도 재연결도 금지: 같은 tmux 세션에 WS 가
|
|
1163
|
+
// 누적 attach 되면 tmux 가 전 클라이언트에 미러링해 글자가 N번씩 보인다.
|
|
1164
|
+
const sock = new WebSocket(proto+'://'+location.host+'/ws/term/'+termRunId
|
|
1162
1165
|
+'?cols='+termObj.cols+'&rows='+termObj.rows);
|
|
1163
|
-
termWS
|
|
1166
|
+
termWS = sock;
|
|
1167
|
+
sock.onopen = ()=>{
|
|
1168
|
+
if (sock!==termWS){ try{ sock.close(); }catch{} return; }
|
|
1164
1169
|
termRetry = 0;
|
|
1165
1170
|
$('termTitle').textContent = ((runs.get(termRunId)||{}).tmuxWindow||'terminal');
|
|
1166
1171
|
termObj.focus();
|
|
1167
1172
|
};
|
|
1168
|
-
|
|
1173
|
+
sock.onmessage = (m)=>{
|
|
1174
|
+
if (sock!==termWS || !termObj) return;
|
|
1169
1175
|
try{
|
|
1170
1176
|
const d = JSON.parse(m.data);
|
|
1171
1177
|
if (d.t==='o') termObj.write(d.d);
|
|
@@ -1173,7 +1179,8 @@ function termConnect(){
|
|
|
1173
1179
|
else if (d.t==='exit') termObj.write('\\r\\n\\x1b[90m[session ended — reconnecting will revive it]\\x1b[0m\\r\\n');
|
|
1174
1180
|
}catch{}
|
|
1175
1181
|
};
|
|
1176
|
-
|
|
1182
|
+
sock.onclose = ()=>{
|
|
1183
|
+
if (sock!==termWS) return;
|
|
1177
1184
|
if (termClosing || termRunId==null) return;
|
|
1178
1185
|
// 예기치 않은 끊김 — 백오프 재연결(서버가 죽은 세션도 소생시킴)
|
|
1179
1186
|
const delay = Math.min(8000, 800 * Math.pow(2, termRetry++));
|
|
@@ -1204,6 +1211,9 @@ function termSwitch(id){
|
|
|
1204
1211
|
}
|
|
1205
1212
|
function openTerm(runId){
|
|
1206
1213
|
const r = runs.get(runId); if(!r) return;
|
|
1214
|
+
// 재진입 방어 — 이전 소켓/타이머가 남아 있으면 정리(중복 attach 방지)
|
|
1215
|
+
if (termRetryTimer){ clearTimeout(termRetryTimer); termRetryTimer=null; }
|
|
1216
|
+
if (termWS){ const old=termWS; termWS=null; try{ old.close(); }catch{} }
|
|
1207
1217
|
termRunId = runId; termClosing = false; termRetry = 0;
|
|
1208
1218
|
$('termRid').textContent = 'r'+runId;
|
|
1209
1219
|
$('termTitle').textContent = (r.tmuxWindow||'terminal');
|