From 32d11de38c489c6402648242c62b0d277369f025 Mon Sep 17 00:00:00 2001 From: Federico Jaramillo Martinez Date: Wed, 17 Jun 2026 01:02:33 +0200 Subject: [PATCH] Rename attachment folder from .pi-web/paste to .pi-web/attachments Attachments can be pasted, dropped, or uploaded, so 'paste' was too narrow. Rename the default folder and filename prefix accordingly and update the changeset reference. --- .changeset/prompt-image-attachments.md | 2 +- src/client/src/components/PromptEditor.ts | 2 +- src/client/src/controllers/sessionController.test.ts | 4 ++-- src/server/sessions/attachmentService.test.ts | 4 ++-- src/server/sessions/attachmentService.ts | 6 +++--- src/server/sessions/sessionRoutes.test.ts | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.changeset/prompt-image-attachments.md b/.changeset/prompt-image-attachments.md index 14010f0..d6adde7 100644 --- a/.changeset/prompt-image-attachments.md +++ b/.changeset/prompt-image-attachments.md @@ -2,4 +2,4 @@ "@jmfederico/pi-web": minor --- -Add image attachments to the chat composer. You can now paste (Ctrl/Cmd+V), drag-and-drop, or use the new Attach button to add PNG, JPEG, GIF, and WebP images to a message, with thumbnail previews and multi-image support. Attachments are delivered to the session using pi's native image format (images are auto-resized to pi's inline limits for full compatibility), and image content now renders inline in the transcript. A per-message delivery toggle also lets you instead save attachments into the workspace `.pi-web/paste` folder and reference them so the agent reads them with its own tools. The accepted HTTP upload size is now configurable via `PI_WEB_MAX_UPLOAD_BYTES` or the `maxUploadBytes` config value. +Add image attachments to the chat composer. You can now paste (Ctrl/Cmd+V), drag-and-drop, or use the new Attach button to add PNG, JPEG, GIF, and WebP images to a message, with thumbnail previews and multi-image support. Attachments are delivered to the session using pi's native image format (images are auto-resized to pi's inline limits for full compatibility), and image content now renders inline in the transcript. A per-message delivery toggle also lets you instead save attachments into the workspace `.pi-web/attachments` folder and reference them so the agent reads them with its own tools. The accepted HTTP upload size is now configurable via `PI_WEB_MAX_UPLOAD_BYTES` or the `maxUploadBytes` config value. diff --git a/src/client/src/components/PromptEditor.ts b/src/client/src/components/PromptEditor.ts index 3d8eb40..27c3221 100644 --- a/src/client/src/components/PromptEditor.ts +++ b/src/client/src/components/PromptEditor.ts @@ -141,7 +141,7 @@ export class PromptEditor extends LitElement { ` : null} diff --git a/src/client/src/controllers/sessionController.test.ts b/src/client/src/controllers/sessionController.test.ts index 1c9609c..2216bad 100644 --- a/src/client/src/controllers/sessionController.test.ts +++ b/src/client/src/controllers/sessionController.test.ts @@ -269,7 +269,7 @@ describe("SessionController", () => { const attachments: PromptAttachment[] = [{ kind: "image", mimeType: "image/png", data: "QUJD", name: "shot.png" }]; const api: typeof defaultApi = { ...defaultApi, - saveAttachments: (_session, sent) => { savedCalledWith = sent; return Promise.resolve([{ path: ".pi-web/paste/shot.png", mimeType: "image/png", size: 3 }]); }, + saveAttachments: (_session, sent) => { savedCalledWith = sent; return Promise.resolve([{ path: ".pi-web/attachments/shot.png", mimeType: "image/png", size: 3 }]); }, prompt: (_session, text, _behavior, _machineId, sentAttachments) => { promptText = text; promptAttachments = sentAttachments; return Promise.resolve({ accepted: true }); }, }; const controller = new SessionController( @@ -283,7 +283,7 @@ describe("SessionController", () => { await controller.send("check this", undefined, attachments, "folder"); expect(savedCalledWith).toEqual(attachments); - expect(promptText).toBe("check this\n\n@.pi-web/paste/shot.png"); + expect(promptText).toBe("check this\n\n@.pi-web/attachments/shot.png"); expect(promptAttachments).toBeUndefined(); expect(state.sendingPrompts).toEqual({}); }); diff --git a/src/server/sessions/attachmentService.test.ts b/src/server/sessions/attachmentService.test.ts index 4d46698..1388bd7 100644 --- a/src/server/sessions/attachmentService.test.ts +++ b/src/server/sessions/attachmentService.test.ts @@ -30,12 +30,12 @@ describe("saveAttachmentsToWorkspace", () => { ); expect(saved).toHaveLength(2); - expect(saved[0]?.path.startsWith(`${DEFAULT_ATTACHMENT_FOLDER}/paste-`)).toBe(true); + expect(saved[0]?.path.startsWith(`${DEFAULT_ATTACHMENT_FOLDER}/attachment-`)).toBe(true); expect(saved[0]?.path.endsWith(".png")).toBe(true); expect(saved[1]?.path.endsWith(".webp")).toBe(true); expect(saved[0]?.size).toBe(pngBytes.byteLength); - const folderEntries = await readdir(join(workspace, ".pi-web", "paste")); + const folderEntries = await readdir(join(workspace, ".pi-web", "attachments")); expect(folderEntries).toHaveLength(2); const firstPath = saved[0]?.path ?? ""; diff --git a/src/server/sessions/attachmentService.ts b/src/server/sessions/attachmentService.ts index 7500e4a..bc4ad2b 100644 --- a/src/server/sessions/attachmentService.ts +++ b/src/server/sessions/attachmentService.ts @@ -10,7 +10,7 @@ import { resolveParentInsideWorkspace } from "../workspaces/pathSafety.js"; * Default workspace-relative folder used when saving pasted/dropped * attachments for the agent to read with its own tools. */ -export const DEFAULT_ATTACHMENT_FOLDER = ".pi-web/paste"; +export const DEFAULT_ATTACHMENT_FOLDER = ".pi-web/attachments"; export interface InlineImage { image: ImageContent; @@ -42,7 +42,7 @@ export async function attachmentsToInlineImages(attachments: PromptAttachment[]) } export interface SaveAttachmentsOptions { - /** Workspace-relative folder to write into. Defaults to `.pi-web/paste`. */ + /** Workspace-relative folder to write into. Defaults to `.pi-web/attachments`. */ folder?: string; /** Clock injection for deterministic tests. */ now?: () => Date; @@ -66,7 +66,7 @@ export async function saveAttachmentsToWorkspace( const saved: SavedPromptAttachment[] = []; for (const [index, attachment] of attachments.entries()) { const bytes = Buffer.from(attachment.data, "base64"); - const filename = `paste-${stamp}-${String(index + 1)}.${extensionForImageMimeType(attachment.mimeType)}`; + const filename = `attachment-${stamp}-${String(index + 1)}.${extensionForImageMimeType(attachment.mimeType)}`; const relativePath = `${folder}/${filename}`; await writeFile(join(folderTarget, filename), bytes); saved.push({ path: relativePath, mimeType: attachment.mimeType, size: bytes.byteLength }); diff --git a/src/server/sessions/sessionRoutes.test.ts b/src/server/sessions/sessionRoutes.test.ts index 7cc4ca7..69562b5 100644 --- a/src/server/sessions/sessionRoutes.test.ts +++ b/src/server/sessions/sessionRoutes.test.ts @@ -175,7 +175,7 @@ class CapturingRouteSessionService extends PiSessionService { override saveAttachments(_lookup: string | PiSessionRef, attachments: unknown, folder?: string) { const list = Array.isArray(attachments) ? attachments : []; return Promise.resolve(list.map((attachment: { mimeType: string; data: string; name?: string }) => ({ - path: `${folder ?? ".pi-web/paste"}/${attachment.name ?? "file.png"}`, + path: `${folder ?? ".pi-web/attachments"}/${attachment.name ?? "file.png"}`, mimeType: attachment.mimeType, size: Buffer.from(attachment.data, "base64").byteLength, })));