feat: add prompt attachment support across sessions
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
||||
} from "@/contexts/session-stream-reducers";
|
||||
import type {
|
||||
ActivityLogPayload,
|
||||
AgentAttachment,
|
||||
AgentStreamEventPayload,
|
||||
SessionOutboundMessage,
|
||||
} from "@server/shared/messages";
|
||||
@@ -300,7 +301,12 @@ function SessionProviderInternal({ children, serverId, client }: SessionProvider
|
||||
|
||||
const previousAgentStatusRef = useRef<Map<string, AgentLifecycleStatus>>(new Map());
|
||||
const sendAgentMessageRef = useRef<
|
||||
((agentId: string, message: string, images?: AttachmentMetadata[]) => Promise<void>) | null
|
||||
((
|
||||
agentId: string,
|
||||
message: string,
|
||||
images?: AttachmentMetadata[],
|
||||
attachments?: AgentAttachment[],
|
||||
) => Promise<void>) | null
|
||||
>(null);
|
||||
const sessionStateTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const attentionNotifiedRef = useRef<Map<string, number>>(new Map());
|
||||
@@ -1584,7 +1590,12 @@ function SessionProviderInternal({ children, serverId, client }: SessionProvider
|
||||
]);
|
||||
|
||||
const sendAgentMessage = useCallback(
|
||||
async (agentId: string, message: string, images?: AttachmentMetadata[]) => {
|
||||
async (
|
||||
agentId: string,
|
||||
message: string,
|
||||
images?: AttachmentMetadata[],
|
||||
attachments?: AgentAttachment[],
|
||||
) => {
|
||||
const messageId = generateMessageId();
|
||||
const userMessage: StreamItem = {
|
||||
kind: "user_message",
|
||||
@@ -1624,6 +1635,7 @@ function SessionProviderInternal({ children, serverId, client }: SessionProvider
|
||||
.sendAgentMessage(agentId, message, {
|
||||
messageId,
|
||||
...(imagesData && imagesData.length > 0 ? { images: imagesData } : {}),
|
||||
...(attachments && attachments.length > 0 ? { attachments } : {}),
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("[Session] Failed to send agent message:", error);
|
||||
@@ -1692,6 +1704,7 @@ function SessionProviderInternal({ children, serverId, client }: SessionProvider
|
||||
config,
|
||||
initialPrompt,
|
||||
images,
|
||||
attachments,
|
||||
git,
|
||||
worktreeName,
|
||||
requestId,
|
||||
@@ -1699,6 +1712,7 @@ function SessionProviderInternal({ children, serverId, client }: SessionProvider
|
||||
config: any;
|
||||
initialPrompt: string;
|
||||
images?: AttachmentMetadata[];
|
||||
attachments?: AgentAttachment[];
|
||||
git?: any;
|
||||
worktreeName?: string;
|
||||
requestId?: string;
|
||||
@@ -1718,6 +1732,7 @@ function SessionProviderInternal({ children, serverId, client }: SessionProvider
|
||||
config,
|
||||
...(trimmedPrompt ? { initialPrompt: trimmedPrompt } : {}),
|
||||
...(imagesData && imagesData.length > 0 ? { images: imagesData } : {}),
|
||||
...(attachments && attachments.length > 0 ? { attachments } : {}),
|
||||
...(git ? { git } : {}),
|
||||
...(worktreeName ? { worktreeName } : {}),
|
||||
...(requestId ? { requestId } : {}),
|
||||
|
||||
@@ -25,6 +25,7 @@ import { normalizeAgentSnapshot } from "@/utils/agent-snapshots";
|
||||
import { encodeImages } from "@/utils/encode-images";
|
||||
import { toErrorMessage } from "@/utils/error-messages";
|
||||
import { openExternalUrl } from "@/utils/open-external-url";
|
||||
import { buildGitHubAttachmentFromSearchItem } from "@/utils/review-attachments";
|
||||
import {
|
||||
requireWorkspaceExecutionAuthority,
|
||||
} from "@/utils/workspace-execution";
|
||||
@@ -32,23 +33,6 @@ import { navigateToPreparedWorkspaceTab } from "@/utils/workspace-navigation";
|
||||
import type { ImageAttachment, MessagePayload } from "@/components/message-input";
|
||||
import type { GitHubSearchItem } from "@server/shared/messages";
|
||||
|
||||
function buildInitialPrompt(userText: string, githubItem: GitHubSearchItem | null): string {
|
||||
const parts: string[] = [];
|
||||
|
||||
if (githubItem) {
|
||||
const kind = githubItem.kind === "pr" ? "Pull Request" : "Issue";
|
||||
const header = `GitHub ${kind} #${githubItem.number}: ${githubItem.title}`;
|
||||
const body = githubItem.body?.trim();
|
||||
parts.push(body ? `${header}\n\n${body}` : header);
|
||||
}
|
||||
|
||||
if (userText) {
|
||||
parts.push(userText);
|
||||
}
|
||||
|
||||
return parts.join("\n\n---\n\n");
|
||||
}
|
||||
|
||||
interface NewWorkspaceScreenProps {
|
||||
serverId: string;
|
||||
sourceDirectory: string;
|
||||
@@ -176,9 +160,11 @@ export function NewWorkspaceScreen({
|
||||
}
|
||||
|
||||
const connectedClient = withConnectedClient();
|
||||
const reviewAttachment = buildGitHubAttachmentFromSearchItem(selectedGithubItem);
|
||||
const payload = await connectedClient.createPaseoWorktree({
|
||||
cwd: sourceDirectory,
|
||||
worktreeSlug: createNameId(),
|
||||
...(reviewAttachment ? { attachments: [reviewAttachment] } : {}),
|
||||
});
|
||||
|
||||
if (payload.error || !payload.workspace) {
|
||||
@@ -193,6 +179,7 @@ export function NewWorkspaceScreen({
|
||||
createdWorkspace,
|
||||
mergeWorkspaces,
|
||||
serverId,
|
||||
selectedGithubItem,
|
||||
sourceDirectory,
|
||||
withConnectedClient,
|
||||
]);
|
||||
@@ -208,8 +195,9 @@ export function NewWorkspaceScreen({
|
||||
throw new Error("Composer state is required");
|
||||
}
|
||||
|
||||
const initialPrompt = buildInitialPrompt(text.trim(), selectedGithubItem);
|
||||
const initialPrompt = text.trim();
|
||||
const encodedImages = await encodeImages(images);
|
||||
const reviewAttachment = buildGitHubAttachmentFromSearchItem(selectedGithubItem);
|
||||
const workspaceDirectory = requireWorkspaceExecutionAuthority({ workspace }).workspaceDirectory;
|
||||
const agent = await connectedClient.createAgent({
|
||||
provider: composerState.selectedProvider,
|
||||
@@ -224,6 +212,7 @@ export function NewWorkspaceScreen({
|
||||
: {}),
|
||||
...(initialPrompt ? { initialPrompt } : {}),
|
||||
...(encodedImages && encodedImages.length > 0 ? { images: encodedImages } : {}),
|
||||
...(reviewAttachment ? { attachments: [reviewAttachment] } : {}),
|
||||
});
|
||||
|
||||
setAgents(serverId, (previous) => {
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { buildGitHubAttachmentFromSearchItem } from "./review-attachments";
|
||||
|
||||
describe("buildGitHubAttachmentFromSearchItem", () => {
|
||||
it("builds a github_pr attachment for pull requests", () => {
|
||||
const attachment = buildGitHubAttachmentFromSearchItem({
|
||||
kind: "pr",
|
||||
number: 123,
|
||||
title: "Fix race in worktree setup",
|
||||
url: "https://github.com/getpaseo/paseo/pull/123",
|
||||
state: "OPEN",
|
||||
body: "PR body",
|
||||
labels: ["bug"],
|
||||
baseRefName: "main",
|
||||
headRefName: "fix/worktree-race",
|
||||
});
|
||||
|
||||
expect(attachment).toEqual({
|
||||
type: "github_pr",
|
||||
mimeType: "application/github-pr",
|
||||
number: 123,
|
||||
title: "Fix race in worktree setup",
|
||||
url: "https://github.com/getpaseo/paseo/pull/123",
|
||||
body: "PR body",
|
||||
baseRefName: "main",
|
||||
headRefName: "fix/worktree-race",
|
||||
});
|
||||
});
|
||||
|
||||
it("builds a github_issue attachment for issues", () => {
|
||||
const attachment = buildGitHubAttachmentFromSearchItem({
|
||||
kind: "issue",
|
||||
number: 55,
|
||||
title: "Improve startup error details",
|
||||
url: "https://github.com/getpaseo/paseo/issues/55",
|
||||
state: "OPEN",
|
||||
body: "Issue body",
|
||||
labels: ["bug"],
|
||||
});
|
||||
|
||||
expect(attachment).toEqual({
|
||||
type: "github_issue",
|
||||
mimeType: "application/github-issue",
|
||||
number: 55,
|
||||
title: "Improve startup error details",
|
||||
url: "https://github.com/getpaseo/paseo/issues/55",
|
||||
body: "Issue body",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns null when no item is selected", () => {
|
||||
expect(buildGitHubAttachmentFromSearchItem(null)).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { AgentAttachment, GitHubSearchItem } from "@server/shared/messages";
|
||||
|
||||
export function buildGitHubAttachmentFromSearchItem(
|
||||
item: GitHubSearchItem | null,
|
||||
): AgentAttachment | null {
|
||||
if (!item) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (item.kind === "pr") {
|
||||
return {
|
||||
type: "github_pr",
|
||||
mimeType: "application/github-pr",
|
||||
number: item.number,
|
||||
title: item.title,
|
||||
url: item.url,
|
||||
...(item.body ? { body: item.body } : {}),
|
||||
...(item.baseRefName ? { baseRefName: item.baseRefName } : {}),
|
||||
...(item.headRefName ? { headRefName: item.headRefName } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: "github_issue",
|
||||
mimeType: "application/github-issue",
|
||||
number: item.number,
|
||||
title: item.title,
|
||||
url: item.url,
|
||||
...(item.body ? { body: item.body } : {}),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user