fix: avoid blocking on the task that just started

When an assistant turn emits both confirm and start_next, confirm may
already start the newly queued proposal. The later start_next then sees
the just-started task as an active blocker and appends a false running-task correction. Suppress that redundant start_next correction when confirm in the same turn already started the current user proposal. Also tighten the worker bootstrap wording to run confirmed low-risk work continuously until a real decision point.
This commit is contained in:
zenord
2026-08-22 00:15:32 +08:00
parent 9ae2639660
commit e9f1d595fa
3 changed files with 20 additions and 0 deletions
+6
View File
@@ -292,6 +292,7 @@ export class AssistantManager implements ConversationRuntime {
private async executeActions(chatKey: string, request: ConversationRequest, actions: AssistantAction[]): Promise<{ corrections: string[]; images?: OutgoingImageRef[] }> {
const corrections: string[] = [];
const images: OutgoingImageRef[] = [];
let confirmStartedOwnProposal = false;
for (const action of actions) {
if (action.type === "create_proposal") {
await this.proposals.create({
@@ -304,6 +305,10 @@ export class AssistantManager implements ConversationRuntime {
} else if (action.type === "confirm") {
const ok = await this.scheduler(() => this.confirmLocked(chatKey, request.userId, action.id));
if (!ok) corrections.push("没有可确认的提案;你只能操作自己发起的任务。");
else {
confirmStartedOwnProposal = this.proposals.list({ status: "working" })
.some((proposal) => proposal.ownerChatKey === chatKey && proposal.requesterUserId === request.userId);
}
} else if (action.type === "adjust_proposal") {
const ok = await this.scheduler(() => this.adjustProposalLocked(chatKey, request.userId, action));
if (!ok) corrections.push("没有可调整的提案;只有还没开始执行的任务可以调整,且你只能操作自己发起的任务。");
@@ -318,6 +323,7 @@ export class AssistantManager implements ConversationRuntime {
if (image) images.push({ path: image.path, mimeType: image.mimeType, filename: image.filename });
else corrections.push("这张图发不出去:路径不在工作区内,或者不是有效的 png/jpg 图片。");
} else if (action.type === "start_next") {
if (confirmStartedOwnProposal) continue;
const started = await this.scheduler(() => this.tryStartLocked(chatKey, request.userId));
if (!started) corrections.push(this.startNextCorrection(chatKey, request.userId));
} else if (action.type === "cancel") {
+13
View File
@@ -721,6 +721,19 @@ test("start_next reports when the queue is empty instead of claiming a start", a
}
});
test("confirm plus start_next in the same assistant turn does not treat the just-started task as a blocker", async () => {
const harness = await createHarness();
try {
await harness.manager.prompt(request("create proposal: hang", "user-a"));
const reply = await harness.manager.prompt(request("confirm and start next", "user-a"));
assert.doesNotMatch(reply.text, /还有一个任务正在执行/);
assert.equal(reply.text, "confirmed");
assert.equal(harness.proposals.list()[0]!.status, "working");
} finally {
await closeHarness(harness);
}
});
test("image attachments reach the assistant as image blocks when the agent supports them", async () => {
const harness = await createHarness();
try {
+1
View File
@@ -85,6 +85,7 @@ const app = acp.agent({ name: "fake-acp-agent" })
const goal = userText.slice("create proposal:".length).trim();
response = assistantEnvelope("proposal drafted", [{ type: "create_proposal", title: "Test proposal", goal, steps: ["step 1"] }]);
} else if (userText === "confirm") response = assistantEnvelope("confirmed", [{ type: "confirm" }]);
else if (userText === "confirm and start next") response = assistantEnvelope("confirmed", [{ type: "confirm" }, { type: "start_next" }]);
else if (userText === "finish") response = assistantEnvelope("finishing", [{ type: "finish" }]);
else if (userText.startsWith("follow up:")) response = assistantEnvelope("following up", [{ type: "follow_up", instruction: userText.slice("follow up:".length).trim() }]);
else if (userText.startsWith("adjust proposal:")) response = assistantEnvelope("adjusting", [{ type: "adjust_proposal", title: userText.slice("adjust proposal:".length).trim() }]);