shellbase 0.9.0 → 0.9.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/dist/agent.js +74 -32
- package/package.json +1 -1
package/dist/agent.js
CHANGED
|
@@ -168,6 +168,13 @@ export async function runAgent(options) {
|
|
|
168
168
|
console.error('전송 실패:', err.message);
|
|
169
169
|
}
|
|
170
170
|
};
|
|
171
|
+
// 이 세션이 보내는 것들을 한 줄로 세운다 — 앞의 것이 끝나야 다음 것이 나간다.
|
|
172
|
+
const enqueueSend = (session, task) => {
|
|
173
|
+
const next = session.sendChain.then(task, task);
|
|
174
|
+
// 하나가 실패해도 줄 전체가 멈추면 안 되므로, 줄에는 성공/실패를 흡수한 것만 남긴다
|
|
175
|
+
session.sendChain = next.then(() => undefined, () => undefined);
|
|
176
|
+
return next;
|
|
177
|
+
};
|
|
171
178
|
const flush = async (session) => {
|
|
172
179
|
session.pendingFlush = null;
|
|
173
180
|
if (!session.outBuffer)
|
|
@@ -175,20 +182,70 @@ export async function runAgent(options) {
|
|
|
175
182
|
const chunk = session.outBuffer;
|
|
176
183
|
session.outBuffer = '';
|
|
177
184
|
session.lastSentAt = Date.now();
|
|
185
|
+
await enqueueSend(session, async () => {
|
|
186
|
+
try {
|
|
187
|
+
await channel.send({
|
|
188
|
+
kind: 'output',
|
|
189
|
+
to: session.deviceId,
|
|
190
|
+
data: encryptFrame(chunk, session.frameKey),
|
|
191
|
+
}, { includeSelf: false });
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
// 실패한 출력을 그냥 버리면 폰 화면이 깨진 채로 남으니, 다음 전송 앞에 다시 붙여 순서대로 재시도한다
|
|
195
|
+
session.outBuffer = chunk + session.outBuffer;
|
|
196
|
+
session.sendPenaltyUntil = Date.now() + SEND_PENALTY_MS;
|
|
197
|
+
console.error('출력 전송 실패, 다시 시도해요:', err.message);
|
|
198
|
+
if (!session.closing)
|
|
199
|
+
scheduleFlush(session);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
};
|
|
203
|
+
// 폰이 "화면을 통째로 다시 달라"고 할 때 (처음 접속·재연결·크기 변경 뒤).
|
|
204
|
+
const sendRestore = async (session) => {
|
|
205
|
+
if (session.restoring) {
|
|
206
|
+
// 보내는 중에 또 요청이 왔다 — 지금 것이 끝난 뒤 한 번만 더 보낸다.
|
|
207
|
+
// 두 벌을 동시에 보내면 조각이 서로 끼어들어 폰 화면에 같은 내용이 두 번 그려진다.
|
|
208
|
+
session.restoreAgain = true;
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
session.restoring = true;
|
|
178
212
|
try {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
213
|
+
do {
|
|
214
|
+
session.restoreAgain = false;
|
|
215
|
+
if (session.closing)
|
|
216
|
+
return;
|
|
217
|
+
// 지금 화면 상태를 통째로 보낼 참이다. 아직 못 보낸 출력(outBuffer)은 이미 그 화면 안에
|
|
218
|
+
// 들어 있다 — pty.onData 가 outBuffer 와 screen 에 같이 넣기 때문. 그대로 두면 복원 직후
|
|
219
|
+
// 같은 내용이 한 번 더 나가서 화면에 두 번 찍힌다. 여기서 버린다.
|
|
220
|
+
// (아래 await 전까지는 끊기지 않고 실행되므로, 버리는 것과 찍어내는 것 사이에 새 출력이
|
|
221
|
+
// 끼어들 틈이 없다)
|
|
222
|
+
if (session.pendingFlush !== null) {
|
|
223
|
+
clearTimeout(session.pendingFlush);
|
|
224
|
+
session.pendingFlush = null;
|
|
225
|
+
}
|
|
226
|
+
session.outBuffer = '';
|
|
227
|
+
let snapshot;
|
|
228
|
+
try {
|
|
229
|
+
snapshot = session.serializer.serialize({ scrollback: RESTORE_SCROLLBACK_LINES });
|
|
230
|
+
}
|
|
231
|
+
catch (err) {
|
|
232
|
+
console.error('화면 복원 준비 실패:', err.message);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
// 화면을 먼저 지우고(2J·3J) 복원 내용을 보내야, 폰에 남아있던 예전 화면과 섞이지 않는다
|
|
236
|
+
const payload = `\x1b[H\x1b[2J\x1b[3J${snapshot}`;
|
|
237
|
+
for (let i = 0; i < payload.length; i += RESTORE_CHUNK) {
|
|
238
|
+
const part = payload.slice(i, i + RESTORE_CHUNK);
|
|
239
|
+
await enqueueSend(session, () => send({
|
|
240
|
+
kind: 'output',
|
|
241
|
+
to: session.deviceId,
|
|
242
|
+
data: encryptFrame(part, session.frameKey),
|
|
243
|
+
}));
|
|
244
|
+
}
|
|
245
|
+
} while (session.restoreAgain);
|
|
184
246
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
session.outBuffer = chunk + session.outBuffer;
|
|
188
|
-
session.sendPenaltyUntil = Date.now() + SEND_PENALTY_MS;
|
|
189
|
-
console.error('출력 전송 실패, 다시 시도해요:', err.message);
|
|
190
|
-
if (!session.closing)
|
|
191
|
-
scheduleFlush(session);
|
|
247
|
+
finally {
|
|
248
|
+
session.restoring = false;
|
|
192
249
|
}
|
|
193
250
|
};
|
|
194
251
|
const scheduleFlush = (session) => {
|
|
@@ -307,6 +364,9 @@ export async function runAgent(options) {
|
|
|
307
364
|
inputApproved: false,
|
|
308
365
|
approvalInFlight: false,
|
|
309
366
|
outBuffer: '',
|
|
367
|
+
sendChain: Promise.resolve(),
|
|
368
|
+
restoring: false,
|
|
369
|
+
restoreAgain: false,
|
|
310
370
|
lastSentAt: 0,
|
|
311
371
|
sendPenaltyUntil: 0,
|
|
312
372
|
pendingFlush: null,
|
|
@@ -456,26 +516,8 @@ export async function runAgent(options) {
|
|
|
456
516
|
if (!session || session.closing)
|
|
457
517
|
return;
|
|
458
518
|
if (frame.kind === 'connect_request') {
|
|
459
|
-
// 화면 보기는 승인 여부와 무관하게 항상 되므로, 승인을 묻기 전에 지금 화면부터 그대로
|
|
460
|
-
|
|
461
|
-
void (async () => {
|
|
462
|
-
let snapshot;
|
|
463
|
-
try {
|
|
464
|
-
snapshot = session.serializer.serialize({ scrollback: RESTORE_SCROLLBACK_LINES });
|
|
465
|
-
}
|
|
466
|
-
catch (err) {
|
|
467
|
-
console.error('화면 복원 준비 실패:', err.message);
|
|
468
|
-
return;
|
|
469
|
-
}
|
|
470
|
-
const payload = `\x1b[H\x1b[2J\x1b[3J${snapshot}`;
|
|
471
|
-
for (let i = 0; i < payload.length; i += RESTORE_CHUNK) {
|
|
472
|
-
await send({
|
|
473
|
-
kind: 'output',
|
|
474
|
-
to: session.deviceId,
|
|
475
|
-
data: encryptFrame(payload.slice(i, i + RESTORE_CHUNK), session.frameKey),
|
|
476
|
-
});
|
|
477
|
-
}
|
|
478
|
-
})();
|
|
519
|
+
// 화면 보기는 승인 여부와 무관하게 항상 되므로, 승인을 묻기 전에 지금 화면부터 그대로 복원해준다
|
|
520
|
+
void sendRestore(session);
|
|
479
521
|
// 승인을 묻지 않는 기본 설정에서는 요청이 올 때마다 곧바로 답한다.
|
|
480
522
|
// (예전엔 "승인 진행 중"이면 건너뛰었는데, 그 사이 온 요청은 영영 답을 못 받아서
|
|
481
523
|
// 폰이 "승인 대기 중" 화면에 갇히는 일이 생겼음 — 자동 승인은 기다릴 게 없으므로 항상 응답)
|