Skip to content

Commit 2766bbd

Browse files
committed
fix(task): don't lose chat box input when resuming a task with Continue
Text and images typed into the chat box were discarded when the user clicked Continue on a resumed task instead of pressing Send, and undo could not bring them back. Fixes #487 Assisted-by: Claude Opus 5.5 with Zoo Code
1 parent 3859e5d commit 2766bbd

2 files changed

Lines changed: 86 additions & 1 deletion

File tree

‎src/core/task/Task.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2985,7 +2985,8 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
29852985
let responseText: string | undefined
29862986
let responseImages: string[] | undefined
29872987

2988-
if (response === "messageResponse") {
2988+
// The Continue button (`yesButtonClicked`) can also carry chat box input.
2989+
if (response === "messageResponse" || text || (images && images.length > 0)) {
29892990
await this.say("user_feedback", text, images)
29902991
responseText = text
29912992
responseImages = images

‎src/core/task/__tests__/Task.persistence.spec.ts‎

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2217,6 +2217,90 @@ describe("Task persistence", () => {
22172217
})
22182218

22192219
describe("resumeTaskFromHistory", () => {
2220+
const userMessageBlock = { type: "text", text: "<user_message>\nAlso fix the tests\n</user_message>" }
2221+
const imageBlock = { type: "image", source: { type: "base64", media_type: "image/png", data: "AAA" } }
2222+
2223+
it.each([
2224+
{
2225+
name: "text and image via Continue",
2226+
response: "yesButtonClicked" as const,
2227+
text: "Also fix the tests",
2228+
images: ["data:image/png;base64,AAA"],
2229+
expected: [userMessageBlock, imageBlock],
2230+
},
2231+
{
2232+
name: "text only via Continue",
2233+
response: "yesButtonClicked" as const,
2234+
text: "Also fix the tests",
2235+
images: [],
2236+
expected: [userMessageBlock],
2237+
},
2238+
{
2239+
name: "image only via Continue",
2240+
response: "yesButtonClicked" as const,
2241+
text: "",
2242+
images: ["data:image/png;base64,AAA"],
2243+
expected: [imageBlock],
2244+
},
2245+
{
2246+
name: "text and image via Send",
2247+
response: "messageResponse" as const,
2248+
text: "Also fix the tests",
2249+
images: ["data:image/png;base64,AAA"],
2250+
expected: [userMessageBlock, imageBlock],
2251+
},
2252+
])("forwards chat box input ($name) to the resumed task", async ({ response, text, images, expected }) => {
2253+
mockReadApiMessages.mockResolvedValue([{ role: "assistant", content: [{ type: "text", text: "Working" }] }])
2254+
const task = new Task({
2255+
provider: mockProvider,
2256+
apiConfiguration: mockApiConfig,
2257+
historyItem: {
2258+
id: "resume-with-input",
2259+
number: 1,
2260+
ts: Date.now(),
2261+
task: "Original task",
2262+
tokensIn: 10,
2263+
tokensOut: 5,
2264+
totalCost: 0.001,
2265+
},
2266+
startTask: false,
2267+
})
2268+
vi.spyOn(task, "ask").mockResolvedValue({ response, text, images })
2269+
const say = vi.spyOn(task, "say").mockResolvedValue(undefined)
2270+
const initiate = vi.spyOn(getTaskPersistenceAccess(task), "initiateTaskLoop").mockResolvedValue(undefined)
2271+
2272+
await getTaskPersistenceAccess(task).resumeTaskFromHistory()
2273+
2274+
expect(say).toHaveBeenCalledWith("user_feedback", text, images)
2275+
expect(initiate).toHaveBeenCalledWith(expected)
2276+
})
2277+
2278+
it("resumes without user feedback when Continue is clicked with an empty chat box", async () => {
2279+
mockReadApiMessages.mockResolvedValue([{ role: "assistant", content: [{ type: "text", text: "Working" }] }])
2280+
const task = new Task({
2281+
provider: mockProvider,
2282+
apiConfiguration: mockApiConfig,
2283+
historyItem: {
2284+
id: "resume-without-input",
2285+
number: 1,
2286+
ts: Date.now(),
2287+
task: "Original task",
2288+
tokensIn: 10,
2289+
tokensOut: 5,
2290+
totalCost: 0.001,
2291+
},
2292+
startTask: false,
2293+
})
2294+
vi.spyOn(task, "ask").mockResolvedValue({ response: "yesButtonClicked" })
2295+
const say = vi.spyOn(task, "say").mockResolvedValue(undefined)
2296+
const initiate = vi.spyOn(getTaskPersistenceAccess(task), "initiateTaskLoop").mockResolvedValue(undefined)
2297+
2298+
await getTaskPersistenceAccess(task).resumeTaskFromHistory()
2299+
2300+
expect(say.mock.calls.filter(([type]) => type === "user_feedback")).toEqual([])
2301+
expect(initiate).toHaveBeenCalledWith([{ type: "text", text: "[TASK RESUMPTION] Resuming task..." }])
2302+
})
2303+
22202304
it.each(["not_found", "invalid", "io_error"] as const)(
22212305
"does not persist when hydration fails with %s",
22222306
async (kind) => {

0 commit comments

Comments
 (0)