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

View File

@@ -226,6 +226,47 @@ describe("shared messages attachments", () => {
]);
});
it("keeps known text attachment context kinds and ignores future ones", () => {
const parsed = SendAgentMessageRequestSchema.parse({
type: "send_agent_message_request",
requestId: "req-text-context",
agentId: "agent-1",
text: "Continue",
attachments: [
{
type: "text",
mimeType: "text/plain",
contextKind: "chat_history",
title: "Chat history",
text: "Earlier context",
},
{
type: "text",
mimeType: "text/plain",
contextKind: "future_context",
title: "Future context",
text: "Future client hint",
},
],
});
expect(parsed.attachments).toEqual([
{
type: "text",
mimeType: "text/plain",
contextKind: "chat_history",
title: "Chat history",
text: "Earlier context",
},
{
type: "text",
mimeType: "text/plain",
title: "Future context",
text: "Future client hint",
},
]);
});
it("keeps known firstAgentContext attachments and drops unknown ones", () => {
const parsed = CreatePaseoWorktreeRequestSchema.parse({
type: "create_paseo_worktree_request",

View File

@@ -843,12 +843,18 @@ export const GitHubIssueAttachmentSchema = z.object({
body: z.string().nullable().optional(),
});
export const TextAttachmentSchema = z.object({
type: z.literal("text"),
mimeType: z.literal("text/plain"),
title: z.string().nullable().optional(),
text: z.string(),
});
export const TextAttachmentSchema = z
.object({
type: z.literal("text"),
mimeType: z.literal("text/plain"),
contextKind: z.string().optional(),
title: z.string().nullable().optional(),
text: z.string(),
})
.transform(({ contextKind, ...attachment }) => ({
...attachment,
...(contextKind === "chat_history" ? { contextKind } : {}),
}));
export const ReviewAttachmentContextLineSchema = z.object({
oldLineNumber: z.number().int().positive().nullable(),
@@ -1274,6 +1280,13 @@ export const FetchAgentTimelineRequestMessageSchema = z.object({
projection: z.enum(["projected", "canonical"]).optional(),
});
export const AgentForkContextRequestMessageSchema = z.object({
type: z.literal("agent.fork_context.request"),
agentId: z.string(),
boundaryMessageId: z.string().optional(),
requestId: z.string(),
});
export const SetAgentModeRequestMessageSchema = z.object({
type: z.literal("set_agent_mode_request"),
agentId: z.string(),
@@ -2060,6 +2073,7 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
RestartServerRequestMessageSchema,
DaemonUpdateRequestMessageSchema,
FetchAgentTimelineRequestMessageSchema,
AgentForkContextRequestMessageSchema,
SetAgentModeRequestMessageSchema,
SetAgentModelRequestMessageSchema,
SetAgentThinkingRequestMessageSchema,
@@ -2335,6 +2349,8 @@ export const ServerInfoStatusPayloadSchema = z
daemonDiagnostics: z.boolean().optional(),
// COMPAT(daemonSelfUpdate): added in v0.1.93, remove gate after 2026-12-13.
daemonSelfUpdate: z.boolean().optional(),
// COMPAT(agentForkContext): added in v0.1.102, remove gate after 2026-12-28.
agentForkContext: z.boolean().optional(),
})
.optional(),
})
@@ -2926,6 +2942,18 @@ export const FetchAgentTimelineResponseMessageSchema = z.object({
}),
});
export const AgentForkContextResponseMessageSchema = z.object({
type: z.literal("agent.fork_context.response"),
payload: z.object({
requestId: z.string(),
agentId: z.string(),
attachment: TextAttachmentSchema.nullable(),
itemCount: z.number().int().nonnegative(),
boundaryMessageId: z.string().nullable(),
error: z.string().nullable(),
}),
});
export const CancelAgentResponseMessageSchema = z.object({
type: z.literal("cancel_agent_response"),
payload: z.object({
@@ -4158,6 +4186,7 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
ArchiveWorkspaceResponseMessageSchema,
FetchAgentResponseMessageSchema,
FetchAgentTimelineResponseMessageSchema,
AgentForkContextResponseMessageSchema,
CancelAgentResponseMessageSchema,
ClearAgentAttentionResponseMessageSchema,
WorkspaceCreateResponseSchema,
@@ -4320,6 +4349,7 @@ export type FetchAgentResponseMessage = z.infer<typeof FetchAgentResponseMessage
export type FetchAgentTimelineResponseMessage = z.infer<
typeof FetchAgentTimelineResponseMessageSchema
>;
export type AgentForkContextResponseMessage = z.infer<typeof AgentForkContextResponseMessageSchema>;
export type CancelAgentResponseMessage = z.infer<typeof CancelAgentResponseMessageSchema>;
export type SendAgentMessageResponseMessage = z.infer<typeof SendAgentMessageResponseMessageSchema>;
export type SetVoiceModeResponseMessage = z.infer<typeof SetVoiceModeResponseMessageSchema>;
@@ -4410,6 +4440,7 @@ export type FetchRecentProviderSessionsRequestMessage = z.infer<
>;
export type FetchWorkspacesRequestMessage = z.infer<typeof FetchWorkspacesRequestMessageSchema>;
export type FetchAgentRequestMessage = z.infer<typeof FetchAgentRequestMessageSchema>;
export type AgentForkContextRequestMessage = z.infer<typeof AgentForkContextRequestMessageSchema>;
export type SendAgentMessageRequest = z.infer<typeof SendAgentMessageRequestSchema>;
export type WaitForFinishRequest = z.infer<typeof WaitForFinishRequestSchema>;
export type DictationStreamStartMessage = z.infer<typeof DictationStreamStartMessageSchema>;