From a1ce5bda991b4fd24d5cd33a414dfed98aabd297 Mon Sep 17 00:00:00 2001 From: Codebuddy Date: Wed, 12 Aug 2026 17:35:01 +0800 Subject: [PATCH] =?utf8?q?feat(console):=20=E5=88=A0=E9=99=A4=E4=BB=BB?= =?utf8?q?=E5=8A=A1=E6=97=B6=E5=8F=AF=E9=80=89=E4=B8=80=E5=B9=B6=E5=88=A0?= =?utf8?q?=E9=99=A4=E5=8E=86=E5=8F=B2=E4=BC=9A=E8=AF=9D=E4=B8=8E=E5=B7=A5?= =?utf8?q?=E4=BD=9C=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit - 删除确认弹窗新增两个复选框:同时删除历史会话(对话记录)、同时删除工作目录 - 后端 /api/tasks/{id} DELETE 新增 delete_session / delete_cwd 查询参数 - 按 session_id 删除 ~/.codebuddy/projects/*/*.jsonl 会话文件(含锁文件) - 按 cwd 删除工作目录,带安全护栏:仅允许 TASK_HOME_ROOT 下的子目录,拒绝删除根目录或越界路径 - 返回 removed_session / removed_cwd / cwd_error 供前端提示 --- backend/app.py | 48 +++++++++++++++++++++++++++++++++++++++++++-- frontend/index.html | 29 +++++++++++++++++++++++++-- 2 files changed, 73 insertions(+), 4 deletions(-) diff --git a/backend/app.py b/backend/app.py index 1d13086..de6b15b 100644 --- a/backend/app.py +++ b/backend/app.py @@ -11,6 +11,7 @@ import os import sys import json import glob +import shutil import uuid import asyncio import signal @@ -532,6 +533,10 @@ async def api_delete_task(task_id: str, request: Request): return JSONResponse({"ok": False, "error": "未授权"}, status_code=401) if task_id in ACTIVE_TASKS: return JSONResponse({"ok": False, "error": "任务正在运行,请先暂停"}, status_code=409) + # 可选项:是否一并删除历史会话文件 / 工作目录 + params = request.query_params + delete_session = params.get("delete_session", "0") in ("1", "true", "yes", "on") + delete_cwd = params.get("delete_cwd", "0") in ("1", "true", "yes", "on") data = load_tasks() t = find_task(data, task_id) if not t: @@ -550,10 +555,49 @@ async def api_delete_task(task_id: str, request: Request): return JSONResponse({"ok": False, "error": f"暂停不足 24 小时,还需 {hours_left:.1f} 小时"}, status_code=409) except (ValueError, TypeError): return JSONResponse({"ok": False, "error": "暂停时间格式错误"}, status_code=409) + + removed_session = [] + removed_cwd = False + cwd_error = None + + # 可选:删除历史会话文件(.jsonl 及其锁文件,跨所有 project 目录) + if delete_session: + sid = t.get("session_id") + if sid: + for f in glob.glob(str(CODEBUDDY_PROJECTS_DIR / "*" / f"{sid}.jsonl*")): + try: + os.remove(f) + removed_session.append(f) + except OSError as e: + logger.warning(f"删除会话文件失败 {f}: {e}") + + # 可选:删除工作目录(安全护栏:必须位于 TASK_HOME_ROOT 下且不等于根目录) + if delete_cwd: + cwd = t.get("cwd") + if cwd: + root = os.path.abspath(TASK_HOME_ROOT) + abs_cwd = os.path.abspath(cwd) + if abs_cwd == root or not abs_cwd.startswith(root + os.sep): + cwd_error = "工作目录不在允许范围内,已拒绝删除" + elif not os.path.exists(abs_cwd): + removed_cwd = True # 已不存在,视为删除成功 + else: + try: + shutil.rmtree(abs_cwd) + removed_cwd = True + except OSError as e: + cwd_error = f"删除工作目录失败: {e}" + data["tasks"] = [x for x in data["tasks"] if x["id"] != task_id] save_tasks(data) - logger.info(f"删除任务: id={task_id}, name={t.get('name')}") - return JSONResponse({"ok": True}) + logger.info(f"删除任务: id={task_id}, name={t.get('name')}, " + f"session={len(removed_session)}个文件, cwd_removed={removed_cwd}") + return JSONResponse({ + "ok": True, + "removed_session": removed_session, + "removed_cwd": removed_cwd, + "cwd_error": cwd_error, + }) @app.post("/api/tasks/{task_id}/pause") diff --git a/frontend/index.html b/frontend/index.html index 4857e0a..c2537f8 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -159,6 +159,10 @@ body { position: fixed; top: 0; left: 0; right: 0; bottom: 0; height: 100%; over #del-modal .del-cancel { background:rgba(255,255,255,0.08); color:#ccc; } #del-modal .del-confirm { background:#ff6b6b; color:#fff; } #del-modal .del-confirm:disabled { background:rgba(255,107,107,0.3); cursor:not-allowed; } +#del-modal .del-opts { text-align:left; margin:0 8px 14px; display:flex; flex-direction:column; gap:10px; } +#del-modal .del-opt { color:#e0e0e0; font-size:14px; display:flex; align-items:center; gap:8px; cursor:pointer; line-height:1.4; } +#del-modal .del-opt input { width:16px; height:16px; flex:none; } +#del-modal .del-opt code { color:#4ecca3; font-size:12px; word-break:break-all; } @@ -194,7 +198,11 @@ body { position: fixed; top: 0; left: 0; right: 0; bottom: 0; height: 100%; over
⚠ 删除任务
-
此操作仅删除任务记录,不删除工作目录与历史会话。
+
+ + +
+
任务记录始终会被删除;勾选的项将一并清除。
@@ -459,6 +467,9 @@ body { position: fixed; top: 0; left: 0; right: 0; bottom: 0; height: 100%; over function startDeleteTask(task) { delState = { task: task, step: 0, timer: null, countdown: 10 }; delMsg.textContent = '即将删除任务 "' + task.name + '"'; + document.getElementById('del-session').checked = false; + document.getElementById('del-cwd').checked = false; + document.getElementById('del-cwd-path').textContent = task.cwd || ''; delModal.style.display = 'flex'; armDelConfirmStep(); } @@ -498,9 +509,23 @@ body { position: fixed; top: 0; left: 0; right: 0; bottom: 0; height: 100%; over const task = delState.task; delConfirm.disabled = true; delCount.textContent = '正在删除...'; - const res = await fetch(API + '/api/tasks/' + task.id, { method: 'DELETE', headers: authHeaders() }); + const delSession = document.getElementById('del-session').checked; + const delCwd = document.getElementById('del-cwd').checked; + const qs = new URLSearchParams({ + delete_session: delSession ? '1' : '0', + delete_cwd: delCwd ? '1' : '0', + }); + const res = await fetch(API + '/api/tasks/' + task.id + '?' + qs.toString(), { method: 'DELETE', headers: authHeaders() }); closeDelModal(); if (!res.ok) { const d = await res.json().catch(()=>({})); alert(d.error || '删除失败'); } + else { + const d = await res.json().catch(()=>({})); + let note = '任务已删除'; + if (d.removed_session && d.removed_session.length) note += ';历史会话 ' + d.removed_session.length + ' 个文件已删'; + if (d.removed_cwd) note += ';工作目录已删'; + if (d.cwd_error) note += '(' + d.cwd_error + ')'; + console.log('[删除] ' + note); + } await renderTasks(); }; -- 2.43.0