Fork assistant turns into new drafts (#1788)

* feat(chat): fork assistant turns into new drafts

Builds chat-history attachments on the daemon and routes them into draft composers so users can continue an assistant turn in an existing workspace tab or New Workspace.

* fix(chat): clean up fork draft handling

* fix(chat): address fork draft review feedback

* fix(server): build fork context from bounded timeline

* fix(app): restore assistant turn footer in streams

* fix(app): preserve fork draft setup

* fix(app): tighten assistant fork footers

* fix(app): lock active fork draft handoff

* fix(app): preserve attachment-only draft retries

* fix(app): unify composer attachment scopes

* fix(app): align chat history attachment labels

* fix(app): centralize attachment pill content

* fix(app): trim fork context for workspace naming
This commit is contained in:
Mohamed Boudra
2026-06-29 18:29:59 +02:00
committed by GitHub
parent e63a971968
commit 85acaceb16
54 changed files with 2364 additions and 312 deletions
@@ -87,4 +87,63 @@ describe("useDraftAgentCreateFlow", () => {
});
expect(onCreateSuccess).toHaveBeenCalledTimes(1);
});
it("allows retrying an empty prompt when the draft still has context attachments", async () => {
const attachment = {
kind: "chat_history",
id: "chat-history-1",
attachment: {
type: "text",
mimeType: "text/plain",
contextKind: "chat_history",
title: "Chat history",
text: "Previous conversation",
},
source: {
serverId: "server-1",
agentId: "agent-source",
},
} as const;
const createRequest = vi.fn(async () => ({
agentId: "agent-1",
result: { id: "agent-1" },
}));
const onCreateSuccess = vi.fn();
const validateBeforeSubmit = vi.fn(() => null);
const { result } = renderHook(() =>
useDraftAgentCreateFlow({
draftId: "draft-1",
getPendingServerId: () => "server-1",
buildDraftAgent: (currentAttempt) => ({ currentAttempt }),
createRequest,
onCreateSuccess,
validateBeforeSubmit,
}),
);
await act(async () => {
await result.current.handleCreateFromInput({
text: " ",
attachments: [attachment],
cwd: "/repo",
});
});
expect(validateBeforeSubmit).toHaveBeenCalledWith({
text: "",
attachments: [attachment],
cwd: "/repo",
});
expect(createRequest).toHaveBeenCalledWith({
attempt: expect.objectContaining({
text: "",
attachments: [attachment.attachment],
}),
text: "",
attachments: [attachment.attachment],
cwd: "/repo",
});
expect(onCreateSuccess).toHaveBeenCalledTimes(1);
});
});