Skip to content

Commit 24c5ab0

Browse files
committed
fix: recover terminal logs and stale playwright lock
1 parent 5ccd855 commit 24c5ab0

2 files changed

Lines changed: 74 additions & 0 deletions

File tree

frontend/src/views/Operation/TerminalItem.vue

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,35 @@ const MISSING_LOG_STORAGE_ERROR = 'Log storage not found.'
4141
const PROCESS_FINISHED_MESSAGE = 'Process finished.'
4242
const PROCESS_NOT_RUNNING_ERROR = 'Process is not running.'
4343
const PROCESS_NOT_FOUND_ERROR = 'Process not found.'
44+
const LOG_CACHE_PREFIX = 'terminalLogCache:'
4445
let currentTimeTimer: ReturnType<typeof setInterval> | null = null
4546
const currentLogKey = ref('')
4647
48+
const getLogCacheKey = (logKey: string) => `${LOG_CACHE_PREFIX}${props.mode}:${logKey}`
49+
50+
const saveLogCache = (logKey: string) => {
51+
if (!logKey) return
52+
try {
53+
localStorage.setItem(getLogCacheKey(logKey), JSON.stringify(logData.value.slice(-200)))
54+
} catch {
55+
// ignore cache errors
56+
}
57+
}
58+
59+
const restoreLogCache = (logKey: string) => {
60+
if (!logKey) return false
61+
try {
62+
const raw = localStorage.getItem(getLogCacheKey(logKey))
63+
if (!raw) return false
64+
const parsed = JSON.parse(raw)
65+
if (!Array.isArray(parsed)) return false
66+
logData.value = parsed
67+
return true
68+
} catch {
69+
return false
70+
}
71+
}
72+
4773
const resolveLogKey = async (projectId?: string) => {
4874
const id = projectId ?? store.selectedBot?.project_id
4975
if (!id) {
@@ -187,6 +213,7 @@ const scrollToBottom = async () => {
187213
188214
const appendLocalLog = async (message: string) => {
189215
logData.value.push({ message })
216+
saveLogCache(currentLogKey.value)
190217
await scrollToBottom()
191218
}
192219
@@ -230,6 +257,7 @@ const getHistoryLogs = async (projectId?: string) => {
230257
if (data) {
231258
if (currentLogKey.value !== logId) return
232259
logData.value = data.detail
260+
saveLogCache(logId)
233261
await scrollToBottom()
234262
}
235263
}
@@ -450,6 +478,7 @@ onMounted(async () => {
450478
451479
if (!store.selectedBot) return
452480
const logKey = await resolveLogKey(store.selectedBot.project_id)
481+
restoreLogCache(logKey)
453482
await getHistoryLogs(logKey)
454483
if (props.mode === 'shell' && !store.selectedBot.is_running) {
455484
await ensureStoppedProjectTerminal(store.selectedBot.project_id)
@@ -472,6 +501,7 @@ watch(
472501
473502
const parsedData: ProcessLog = JSON.parse(rawData.toString())
474503
logData.value.push(parsedData)
504+
saveLogCache(currentLogKey.value)
475505
if (parsedData.message === PROCESS_FINISHED_MESSAGE) {
476506
await syncSelectedBotStatus()
477507
}

nb_cli_plugin_webui/app/process/service.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import asyncio
55
import shlex
66
import json
7+
import time
78
from typing import Dict, List, Optional, Tuple
89
from pathlib import Path
910

@@ -48,6 +49,7 @@
4849
PROCESS_START_STABILITY_SECONDS = 2.0
4950
PROJECT_READY_TIMEOUT_SECONDS = 90.0
5051
PROJECT_SHELL_LOG_SUFFIX = ":shell"
52+
PLAYWRIGHT_DIRLOCK_STALE_SECONDS = 5 * 60
5153

5254

5355
class ProjectShellSessionManager:
@@ -181,6 +183,46 @@ def _default_nonebot2_data_dir(env: dict) -> Path:
181183
return home_dir / ".local" / "share" / "nonebot2"
182184

183185

186+
def _clear_stale_htmlrender_dirlock(env: dict, log_storage: Optional[LogStorage] = None) -> None:
187+
lock_path = (
188+
_default_nonebot2_data_dir(env)
189+
/ "nonebot_plugin_htmlrender"
190+
/ "__dirlock"
191+
)
192+
if not lock_path.exists():
193+
return
194+
195+
try:
196+
age_seconds = max(0.0, time.time() - lock_path.stat().st_mtime)
197+
except OSError:
198+
age_seconds = PLAYWRIGHT_DIRLOCK_STALE_SECONDS + 1
199+
200+
if age_seconds < PLAYWRIGHT_DIRLOCK_STALE_SECONDS:
201+
return
202+
203+
try:
204+
if lock_path.is_dir():
205+
import shutil
206+
207+
shutil.rmtree(lock_path, ignore_errors=True)
208+
else:
209+
lock_path.unlink(missing_ok=True)
210+
if log_storage is not None:
211+
asyncio.create_task(
212+
log_storage.add_log(
213+
CustomLog(
214+
level="WARNING",
215+
message=(
216+
"检测到遗留的 Playwright 安装锁文件,已自动清理:"
217+
f" {lock_path}"
218+
),
219+
)
220+
)
221+
)
222+
except Exception:
223+
pass
224+
225+
184226
def _apply_htmlrender_browser_path(env: dict, project_meta, project_dir: Path) -> None:
185227
if str(env.get("PLAYWRIGHT_BROWSERS_PATH") or "").strip():
186228
return
@@ -446,6 +488,8 @@ async def _ensure_htmlrender_browser_ready(
446488
if not _project_mentions_htmlrender(project_meta, project_dir):
447489
return
448490

491+
_clear_stale_htmlrender_dirlock(env, log_storage=log_storage)
492+
449493
try:
450494
browser_exists, executable_path = await _detect_playwright_chromium_executable(
451495
project_dir, env

0 commit comments

Comments
 (0)