From 4aef8ef20a4ab4101fc07fedad41646c8ee9c54a Mon Sep 17 00:00:00 2001 From: Codebuddy Date: Fri, 14 Aug 2026 07:57:02 +0800 Subject: [PATCH] =?utf8?q?feat:=20=E6=8B=86=E5=88=86=E5=90=8E=E5=8F=B0?= =?utf8?q?=E7=8A=B6=E6=80=81=E4=B8=BA=E8=BF=90=E8=A1=8C=E4=B8=AD/=E7=AD=89?= =?utf8?q?=E5=BE=85=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit - 后端 drain 记录 last_output_ts,/api/tasks 按 IDLE_THRESHOLD(2.5s) 区分 background(后台运行中) 与新增 background_idle(后台等待中) - 前端新增 tc-waiting 样式与 background_idle 渲染(⏸ 后台等待中) - 解决关窗后交互式会话仍显示'后台运行中'的误导问题 --- backend/app.py | 12 +++++++++++- frontend/index.html | 3 +++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/backend/app.py b/backend/app.py index e290f99..3314f99 100644 --- a/backend/app.py +++ b/backend/app.py @@ -312,6 +312,7 @@ async def _drain(entry: dict) -> None: text = data.decode("latin-1", errors="replace") entry["buffer"].append(text) entry["buflen"] += len(text) + entry["last_output_ts"] = time.time() _trim_buffer(entry) ws = entry.get("ws") if ws is not None: @@ -411,6 +412,7 @@ def _launch_process(task: dict, sid: str, model: str, cwd: str, codebuddy_path: "name": task["name"], "cwd": cwd, "model": model, "started_at": time.time(), "buffer": [], "buflen": 0, "ws": None, "done": False, "exit_code": None, + "last_output_ts": time.time(), "send_lock": asyncio.Lock(), } RUNNING[task["id"]] = entry @@ -479,6 +481,10 @@ async def api_models(request: Request): }) +# 进程活着但无 WS 时,距上次 PTY 输出超过该秒数即视为“后台等待中”(已输出完、等下一条指令) +IDLE_THRESHOLD = 2.5 + + @app.get("/api/tasks") async def api_tasks(request: Request): if not _auth_ok(request): @@ -494,7 +500,11 @@ async def api_tasks(request: Request): elif t["id"] in RUNNING: e = RUNNING[t["id"]] if e["proc"].poll() is None: - tt["live_status"] = "background" + last_ts = e.get("last_output_ts") or 0 + if (time.time() - last_ts) < IDLE_THRESHOLD: + tt["live_status"] = "background" + else: + tt["live_status"] = "background_idle" else: # 后台跑完了但当时没人在看 → 提示用户回来确认 tt["live_status"] = "done_detached" diff --git a/frontend/index.html b/frontend/index.html index e84124f..9e6f0c5 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -132,6 +132,8 @@ body { position: fixed; top: 0; left: 0; right: 0; bottom: 0; height: 100%; over .task-card.tc-connected .tc-status { color: #ffe66d; } .task-card.tc-background { border-color: #4ecca3; } .task-card.tc-background .tc-status { color: #4ecca3; } +.task-card.tc-waiting { border-color: #74c0fc; } +.task-card.tc-waiting .tc-status { color: #74c0fc; } .task-card.tc-paused .tc-status { color: #ffd866; } .task-card.tc-idle .tc-status { color: #777; } .task-card.tc-done { border-color: #ffa94d; } @@ -378,6 +380,7 @@ body { position: fixed; top: 0; left: 0; right: 0; bottom: 0; height: 100%; over let statusLine, statusClass; if (ls === 'connected') { statusLine = '● 前台已登录'; statusClass = 'tc-connected'; } else if (ls === 'background') { statusLine = '⚙ 后台运行中'; statusClass = 'tc-background'; } + else if (ls === 'background_idle') { statusLine = '⏸ 后台等待中'; statusClass = 'tc-waiting'; } else if (ls === 'done_detached') { statusLine = '⏳ 后台任务待确认' + (t.exit_code != null ? '(退出码 ' + t.exit_code + ')' : ''); statusClass = 'tc-done'; } else if (isPaused) { statusLine = '⏸ 已暂停'; statusClass = 'tc-paused'; } else { statusLine = '○ 空闲'; statusClass = 'tc-idle'; } -- 2.43.0