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
@@ -0,0 +1,50 @@
import type { AgentAttachment } from "@getpaseo/protocol/messages";
function isLikelyWindowsPath(path: string): boolean {
return /^[a-zA-Z]:\//.test(path);
}
function isChatHistoryTextAttachment(attachment: AgentAttachment): boolean {
return attachment.type === "text" && attachment.contextKind === "chat_history";
}
export function getWorkspaceNamingAttachments(
attachments: readonly AgentAttachment[],
): AgentAttachment[] {
return attachments.filter((attachment) => !isChatHistoryTextAttachment(attachment));
}
export function remapDraftCwdToWorkspace(input: {
cwd: string;
sourceDirectory?: string | null;
workspaceDirectory: string;
}): string {
const cwd = input.cwd.trim();
const sourceDirectory = input.sourceDirectory?.trim();
const workspaceDirectory = input.workspaceDirectory.trim();
if (!cwd || !sourceDirectory) {
return workspaceDirectory;
}
const normalizedCwd = cwd.replace(/\\/g, "/").replace(/\/+$/, "");
const normalizedSource = sourceDirectory.replace(/\\/g, "/").replace(/\/+$/, "");
const compareCaseInsensitively =
isLikelyWindowsPath(normalizedCwd) || isLikelyWindowsPath(normalizedSource);
const comparableCwd = compareCaseInsensitively ? normalizedCwd.toLowerCase() : normalizedCwd;
const comparableSource = compareCaseInsensitively
? normalizedSource.toLowerCase()
: normalizedSource;
if (comparableCwd === comparableSource) {
return workspaceDirectory;
}
const relativePath = comparableCwd.startsWith(`${comparableSource}/`)
? normalizedCwd.slice(normalizedSource.length + 1)
: "";
if (!relativePath) {
return workspaceDirectory;
}
const separator =
workspaceDirectory.includes("\\") && !workspaceDirectory.includes("/") ? "\\" : "/";
return [workspaceDirectory.replace(/[\\/]+$/, ""), ...relativePath.split("/")]
.filter(Boolean)
.join(separator);
}