* 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
51 lines
1.8 KiB
TypeScript
51 lines
1.8 KiB
TypeScript
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);
|
|
}
|