feat: 旧任务继续会话——终端任务带新消息 + 全部选项同一会话续跑
server:POST /tasks/{id}/continue(终端非 merged 任务重排队,
resume_message 列存本轮消息,不传选项沿用原值,plan/goal 冲突
与新建一致,goal_mode=1 时转新 goal 语义;CAS 防并发改状态,
审计 task_continued);tasks.resume_message 三处迁移 + PG 建表。
worker:认领后本轮 prompt 换成续跑消息(只换内存副本),
goal 续跑走 goal-resume 而不是 goal,跑完清 resume_message
(进程被杀时不清,下次认领重跑同一条消息)。
host:goal-resume --goal-text(旧任务接续 goal 模式:跳过状态
与记录守卫,旧线程上从第 1 轮开新目标循环,新语义不限轮次;
无线程时拒绝)。
sandbox:Local/Docker goal_resume_task + popen goal-resume kind;
附带 popen goal 命令改成新语义(无验收不传 --verify,轮次 0
原样传,此前 goal 任务到本地 worker 会直接报错)。
console:详情页终端非 merged 任务出继续会话表单(消息 +
模型/强度/权限/计划/goal,模型下拉复用中转列表)。
测试:新增 test_task_continue.py 11 个 + test_host_continue.py 2 个。
This commit is contained in:
1 parent
cf293914d9
commit
3a4997d346
9 files changed
+823
-20
No files matched your search
+56
-4
@@ -215,6 +215,30 @@
|
||||
<input id="forkMsg" type="text" placeholder="分支首轮消息(必填,带历史的新会话)" style="width:100%; box-sizing:border-box; margin:6px 0;">
|
||||
<div class="bar"><button id="btnForkConfirm" class="primary">创建分支任务</button><span id="forkMsgSpan" class="muted"></span></div>
|
||||
</div>
|
||||
<div id="continueForm" style="display:none">
|
||||
<h3>继续会话(旧任务接回来,同一会话再跑一轮;不传的选项沿用任务原值)</h3>
|
||||
<textarea id="continueText" placeholder="本轮要说的话(必填)"></textarea>
|
||||
<div class="bar">
|
||||
<select id="continueModel"><option value="">模型:沿用任务原值</option></select>
|
||||
<select id="continueEffort">
|
||||
<option value="">强度:沿用任务原值</option>
|
||||
<option value="low">low</option>
|
||||
<option value="medium">medium</option>
|
||||
<option value="high">high</option>
|
||||
<option value="xhigh">xhigh</option>
|
||||
<option value="max">max</option>
|
||||
</select>
|
||||
<select id="continueApproval">
|
||||
<option value="">权限:沿用任务原值</option>
|
||||
<option value="ask">请求批准</option>
|
||||
<option value="auto">帮我批准</option>
|
||||
<option value="full">完全访问</option>
|
||||
</select>
|
||||
<label><input type="checkbox" id="continuePlan"> 计划模式(只读)</label>
|
||||
<label><input type="checkbox" id="continueGoal"> goal 模式(不限轮次,模型宣告完成)</label>
|
||||
</div>
|
||||
<div class="bar"><button id="btnContinueConfirm" class="primary">继续会话</button><span id="continueMsgSpan" class="muted"></span></div>
|
||||
</div>
|
||||
<pre id="detailBody" style="display:none"></pre>
|
||||
<h3>实时链路</h3>
|
||||
<div id="traceBox" class="muted">点任务行查看详情后,这里显示过程链路</div>
|
||||
@@ -280,10 +304,12 @@ async function loadRelayModels() {
|
||||
try {
|
||||
var d = await api("/relay/models", "GET");
|
||||
var models = d.models || [];
|
||||
sel.innerHTML = "<option value=''>模型:默认(跟随配置)</option>" +
|
||||
models.map(function (m) {
|
||||
return "<option value='" + esc(m) + "'>" + esc(m) + "</option>";
|
||||
}).join("");
|
||||
var opts = models.map(function (m) {
|
||||
return "<option value='" + esc(m) + "'>" + esc(m) + "</option>";
|
||||
}).join("");
|
||||
sel.innerHTML = "<option value=''>模型:默认(跟随配置)</option>" + opts;
|
||||
var csel = $("#continueModel");
|
||||
if (csel) csel.innerHTML = "<option value=''>模型:沿用任务原值</option>" + opts;
|
||||
$("#modelCustom").style.display = "none";
|
||||
} catch (e) {
|
||||
$("#modelCustom").style.display = "inline";
|
||||
@@ -529,6 +555,8 @@ async function showDetail(taskId) {
|
||||
$("#forkMsgSpan").textContent = "";
|
||||
$("#followupMsg").textContent = "";
|
||||
$("#followupForm").style.display = ["queued", "running"].indexOf(t.status) >= 0 ? "block" : "none";
|
||||
$("#continueMsgSpan").textContent = "";
|
||||
$("#continueForm").style.display = (TRACE_TERMINAL.indexOf(t.status) >= 0 && t.status !== "merged") ? "block" : "none";
|
||||
startTrace(taskId, t.status);
|
||||
loadFollowups(taskId);
|
||||
} catch (e) { $("#detail").innerHTML = "<span class='err'>加载失败:" + esc(e.message) + "</span>"; }
|
||||
@@ -723,6 +751,30 @@ $("#btnForkConfirm").onclick = async function () {
|
||||
loadTasks();
|
||||
} catch (e) { $("#forkMsgSpan").innerHTML = "<span class='err'>分支失败:" + esc(e.message) + "</span>"; }
|
||||
};
|
||||
$("#btnContinueConfirm").onclick = async function () {
|
||||
if (!currentTask) return;
|
||||
var msg = $("#continueText").value.trim();
|
||||
if (!msg) { $("#continueMsgSpan").textContent = "本轮消息必填"; return; }
|
||||
var body = { message: msg };
|
||||
var model = $("#continueModel").value;
|
||||
if (model) body.model = model;
|
||||
var eff = $("#continueEffort").value;
|
||||
if (eff) body.effort = eff;
|
||||
var ap = $("#continueApproval").value;
|
||||
if (ap) {
|
||||
if (ap === "full" && !confirm("完全访问 = 自动批准 + 不受工作区限制,agent 可读写工作区外路径。确认继续吗?")) return;
|
||||
body.approval = ap;
|
||||
}
|
||||
if ($("#continuePlan").checked) body.plan = 1;
|
||||
if ($("#continueGoal").checked) body.goal_mode = 1;
|
||||
try {
|
||||
var t = await api("/tasks/" + encodeURIComponent(currentTask.id) + "/continue", "POST", body);
|
||||
$("#continueMsgSpan").innerHTML = "<span class='ok'>已重新排队,同一会话继续</span>";
|
||||
$("#continueText").value = "";
|
||||
showDetail(currentTask.id);
|
||||
loadTasks();
|
||||
} catch (e) { $("#continueMsgSpan").innerHTML = "<span class='err'>继续失败:" + esc(e.message) + "</span>"; }
|
||||
};
|
||||
$("#btnQueueFollowup").onclick = function () { postFollowup("queued"); };
|
||||
$("#btnDirectFollowup").onclick = function () { postFollowup("direct"); };
|
||||
$("#btnCost").onclick = async function () {
|
||||
|
||||
Reference in new issue
Block a user