Spaces:
Paused
Paused
Keep UTF-8 terminal output intact across packets
Browse files
app.py
CHANGED
|
@@ -10,6 +10,7 @@ import termios
|
|
| 10 |
import asyncio
|
| 11 |
import json
|
| 12 |
import base64
|
|
|
|
| 13 |
from collections import deque
|
| 14 |
import logging
|
| 15 |
import uuid
|
|
@@ -923,6 +924,7 @@ class TerminalSession:
|
|
| 923 |
self.buffer = deque() # 有界輸出回放區,保留序號供重連補播
|
| 924 |
self.buffer_size = 0
|
| 925 |
self.output_seq = 0
|
|
|
|
| 926 |
self.subscribers: dict[int, asyncio.Queue] = {}
|
| 927 |
self._next_sub_id = 0
|
| 928 |
self.cleanup_task: asyncio.Task | None = None
|
|
@@ -952,16 +954,20 @@ class TerminalSession:
|
|
| 952 |
await kill_session(self.id, loop, only_if_idle=True)
|
| 953 |
|
| 954 |
def feed(self, data: bytes):
|
|
|
|
|
|
|
|
|
|
| 955 |
self.output_seq += 1
|
| 956 |
-
|
| 957 |
-
self.
|
|
|
|
| 958 |
while self.buffer_size > SESSION_BUFFER_MAX and self.buffer:
|
| 959 |
_seq, old_data = self.buffer.pop(0)
|
| 960 |
self.buffer_size -= len(old_data)
|
| 961 |
dead = []
|
| 962 |
for sid, q in self.subscribers.items():
|
| 963 |
try:
|
| 964 |
-
q.put_nowait((self.output_seq,
|
| 965 |
except Exception:
|
| 966 |
dead.append(sid)
|
| 967 |
for sid in dead:
|
|
@@ -1150,12 +1156,12 @@ async def terminal_ws(websocket: WebSocket):
|
|
| 1150 |
except Exception:
|
| 1151 |
pass
|
| 1152 |
break
|
| 1153 |
-
seq,
|
| 1154 |
try:
|
| 1155 |
await websocket.send_text(
|
| 1156 |
json.dumps({
|
| 1157 |
"type": "output", "seq": seq,
|
| 1158 |
-
"data":
|
| 1159 |
})
|
| 1160 |
)
|
| 1161 |
except Exception:
|
|
@@ -1173,7 +1179,7 @@ async def terminal_ws(websocket: WebSocket):
|
|
| 1173 |
continue
|
| 1174 |
await websocket.send_text(json.dumps({
|
| 1175 |
"type": "output", "seq": seq,
|
| 1176 |
-
"data": chunk.decode("utf-8", errors="
|
| 1177 |
}))
|
| 1178 |
await websocket.send_text(json.dumps({
|
| 1179 |
"type": "system",
|
|
|
|
| 10 |
import asyncio
|
| 11 |
import json
|
| 12 |
import base64
|
| 13 |
+
import codecs
|
| 14 |
from collections import deque
|
| 15 |
import logging
|
| 16 |
import uuid
|
|
|
|
| 924 |
self.buffer = deque() # 有界輸出回放區,保留序號供重連補播
|
| 925 |
self.buffer_size = 0
|
| 926 |
self.output_seq = 0
|
| 927 |
+
self.utf8_decoder = codecs.getincrementaldecoder("utf-8")(errors="replace")
|
| 928 |
self.subscribers: dict[int, asyncio.Queue] = {}
|
| 929 |
self._next_sub_id = 0
|
| 930 |
self.cleanup_task: asyncio.Task | None = None
|
|
|
|
| 954 |
await kill_session(self.id, loop, only_if_idle=True)
|
| 955 |
|
| 956 |
def feed(self, data: bytes):
|
| 957 |
+
text = self.utf8_decoder.decode(data, final=False)
|
| 958 |
+
if not text:
|
| 959 |
+
return
|
| 960 |
self.output_seq += 1
|
| 961 |
+
encoded = text.encode("utf-8")
|
| 962 |
+
self.buffer.append((self.output_seq, encoded))
|
| 963 |
+
self.buffer_size += len(encoded)
|
| 964 |
while self.buffer_size > SESSION_BUFFER_MAX and self.buffer:
|
| 965 |
_seq, old_data = self.buffer.pop(0)
|
| 966 |
self.buffer_size -= len(old_data)
|
| 967 |
dead = []
|
| 968 |
for sid, q in self.subscribers.items():
|
| 969 |
try:
|
| 970 |
+
q.put_nowait((self.output_seq, text))
|
| 971 |
except Exception:
|
| 972 |
dead.append(sid)
|
| 973 |
for sid in dead:
|
|
|
|
| 1156 |
except Exception:
|
| 1157 |
pass
|
| 1158 |
break
|
| 1159 |
+
seq, text = item
|
| 1160 |
try:
|
| 1161 |
await websocket.send_text(
|
| 1162 |
json.dumps({
|
| 1163 |
"type": "output", "seq": seq,
|
| 1164 |
+
"data": text,
|
| 1165 |
})
|
| 1166 |
)
|
| 1167 |
except Exception:
|
|
|
|
| 1179 |
continue
|
| 1180 |
await websocket.send_text(json.dumps({
|
| 1181 |
"type": "output", "seq": seq,
|
| 1182 |
+
"data": chunk.decode("utf-8", errors="strict"),
|
| 1183 |
}))
|
| 1184 |
await websocket.send_text(json.dumps({
|
| 1185 |
"type": "system",
|