fix(ai): stop resumed companion turns from asking answered questions again (#638)
Resuming an interrupted companion turn kept the answered questions in questions_json, and the client-direct runner ended any run with leftover questions as interrupted. The question form reappeared after the reply. Clear questions when a turn resumes, as the server-side runner did before client-direct calls, and only pause when this run actually asks. Fixes #637 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d2a87a74a6
commit
00ddaa0fed
4 files changed
+46
-2
No files matched your search
@@ -217,7 +217,9 @@ export const resumeCompanionTurn = async (db: DatabaseAdapter, scope: CompanionS
|
||||
await listCompanionTurns(db, scope);
|
||||
const row = await getCompanionTurn(db, scope, id);
|
||||
if (!row || row.status !== "interrupted") throw new AppError("companion_resume_unavailable", "This request cannot be continued.", 409);
|
||||
const result = await db.prepare(`UPDATE companion_turns SET status = 'running', expires_at = ?, answers_json = COALESCE(?, answers_json)
|
||||
// Answered questions are resolved; keeping them would end the resumed run as interrupted again.
|
||||
const result = await db.prepare(`UPDATE companion_turns SET status = 'running', expires_at = ?, answers_json = COALESCE(?, answers_json),
|
||||
questions_json = '[]'
|
||||
WHERE workspace_id = ? AND owner_id = ? AND id = ? AND status = 'interrupted'`).bind(
|
||||
companionTurnLease(), answers ? JSON.stringify(answers.slice(0, 3)) : null, ...bindScope(scope), id).run();
|
||||
if (Number(result.meta.changes) !== 1) throw new AppError("companion_busy", "A conversation is already running or this request was already submitted. Refresh to recover its result.", 409);
|
||||
|
||||
@@ -434,6 +434,32 @@ describe("companion client-direct HTTP contracts", () => {
|
||||
expect((await getCompanionTurn(setup.db, scope, payload.id)).status).toBe("completed");
|
||||
});
|
||||
|
||||
test("resume clears answered questions so the resumed turn can complete (#637)", async () => {
|
||||
const setup = fixture({ loadCredentials: async () => credentials });
|
||||
const payload = input({ message: "翻译这篇笔记", allowNotes: true });
|
||||
expect((await setup.request("turns/prepare", "POST", payload)).status).toBe(200);
|
||||
const asked = await setup.request(`turns/${payload.id}/tools`, "POST", {
|
||||
name: "ask_user_question",
|
||||
input: { questions: [{ id: "language", prompt: "翻译成哪种语言?", inputType: "single_select", options: [{ id: "en", label: "English" }] }] },
|
||||
response: "", process: "",
|
||||
});
|
||||
const askedBody = await asked.json();
|
||||
expect(askedBody.pause).toBe(true);
|
||||
expect((await setup.request(`turns/${payload.id}/complete`, "POST", { response: "", status: "interrupted" })).status).toBe(200);
|
||||
expect((await setup.request(`turns/${payload.id}/resume/prepare`, "POST", {})).status).toBe(400);
|
||||
|
||||
const resumed = await setup.request(`turns/${payload.id}/resume/prepare`, "POST", {
|
||||
answers: [{ questionId: "language", optionIds: ["en"] }],
|
||||
});
|
||||
expect(resumed.status).toBe(200);
|
||||
const prepared = await resumed.json();
|
||||
expect(prepared.turn.questions).toEqual([]);
|
||||
expect(prepared.messages.at(-1).content).toContain("\"optionIds\":[\"en\"]");
|
||||
|
||||
const completed = await setup.request(`turns/${payload.id}/complete`, "POST", { response: "Translated note.", status: "completed" });
|
||||
expect((await completed.json()).turn).toMatchObject({ status: "completed", questions: [], response: "Translated note." });
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("actual AI SDK companion runtime", () => {
|
||||
|
||||
Reference in new issue
Block a user