import sys
import json
import glob
+import shutil
import uuid
import asyncio
import signal
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:
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
+
+ # 可选:删除历史会话文件(<session_id>.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")
#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; }
</style>
</head>
<body>
<div class="del-dialog">
<div class="del-title">⚠ 删除任务</div>
<div class="del-msg" id="del-msg"></div>
- <div class="del-warn">此操作仅删除任务记录,不删除工作目录与历史会话。</div>
+ <div class="del-opts">
+ <label class="del-opt"><input type="checkbox" id="del-session"> 同时删除历史会话(对话记录)</label>
+ <label class="del-opt"><input type="checkbox" id="del-cwd"> 同时删除工作目录 <code id="del-cwd-path"></code></label>
+ </div>
+ <div class="del-warn">任务记录始终会被删除;勾选的项将一并清除。</div>
<div class="del-count" id="del-count"></div>
<div class="del-actions">
<button class="del-btn del-cancel" id="del-cancel">取消</button>
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();
}
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();
};